欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SpringBoot框架入门及使用教程(微服务学习笔记)

程序员文章站 2022-06-12 23:07:59
...

#SpringBoot入门

1、Spring Boot 简介

简化Spring应用开发的一个框架;

整个Spring技术栈的一个大整合;

J2EE开发的一站式解决方案;

2、微服务简介

微服务:架构风格(服务微化)

一个应用应该是一组小型服务;可以通过HTTP的方式进行互通;

单体应用:ALL IN ONE

微服务:每一个功能元素最终都是一个可独立替换和独立升级的软件单元;

详细参照微服务文档

3、Spring Boot HelloWorld

  • 构建maven项目
  • 配置工程pom文件
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.5.RELEASE</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

<profiles>
    <profile>
        <id>jdk-1.8</id>
        <activation>
            <activeByDefault>true</activeByDefault>
            <jdk>1.8</jdk>
        </activation>
        <properties>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
            <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
        </properties>
    </profile>

</profiles>
  • 编写主程序:启动Spring Boot应用
/**
 *  @SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用
 */
@SpringBootApplication
public class HelloWorldMainApplication {

    public static void main(String[] args) {

        // Spring应用启动起来
        SpringApplication.run(HelloWorldMainApplication.class,args);
    }
}
  • 编写相关的Controller、Service
@Controller
public class HelloController {

    @ResponseBody
    @RequestMapping("/hello")
    public String hello(){
        return "Hello World!";
    }
}
  • 运行主程序测试
2018-09-30 10:34:56.756  WARN 776 --- [           main] ionWarningsApplicationContextInitializer : 

** WARNING ** : Your ApplicationContext is unlikely to start due to a @ComponentScan of the default package.
//异常原因:application.java 文件不能直接放在main/java文件夹下,必须要建一个包把他放进去
  • 简化部署
 <!-- 这个插件,可以将应用打包成一个可执行的jar包;-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

将这个应用打成jar包,直接使用java -jar的命令进行执行;

4、Hello World探究

1、POM文件

1、父项目

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.5.RELEASE</version>
</parent>

他的父项目是
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.0.5.RELEASE</version>
  <relativePath>../../spring-boot-dependencies</relativePath>
</parent>
他负责管理Spring Boot应用里面的所有依赖版本;

Spring Boot的版本仲裁中心;

​ 以后我们导入依赖默认是不需要写版本;(没有在dependencies里面管理的依赖自然需要声明版本号)

2、启动器

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

spring-boot-starter-web

​ spring-boot-starter:spring-boot场景启动器之一;帮我们导入了web模块正常运行所依赖的组件;

Spring Boot将所有的功能场景都抽取出来,做成一个个的starters(启动器),只需要在项目里面引入这些starter相关场景的所有依赖都会导入进来。要用什么功能就导入什么场景的启动器

2、主程序类,主入口类

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

    public static void main(String[] args) {

        // Spring应用启动起来
        SpringApplication.run(HelloWorldMainApplication.class,args);
    }
}

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
      @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

@SpringBootConfiguration:Spring Boot的配置类;

​ 标注在某个类上,表示这是一个Spring Boot的配置类;

​ @Configuration:配置类上来标注这个注解;

​ 配置类 ----- 配置文件;配置类也是容器中的一个组件;@Component

@EnableAutoConfiguration:开启自动配置功能;

​ 以前我们需要配置的东西,Spring Boot帮我们自动配置;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能;这样自动配置才能生效;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

@AutoConfigurationPackage:自动配置包

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

​ Spring的底层注解@Import,给容器中导入一个组件;导入的组件由AutoConfigurationPackages.Registrar.class;

将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器;

​ @Import(EnableAutoConfigurationImportSelector.class);

​ 给容器中导入组件?

EnableAutoConfigurationImportSelector:导入哪些组件的选择器;

​ 将所有需要导入的组件以全类名的方式返回;这些组件就会被添加到容器中;

​ 会给容器中导入非常多的自动配置类(xxxAutoConfiguration);就是给容器中导入这个场景需要的所有组件,并配置好这些组件;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-liyTHXAr-1605780851786)(./image/#自动配置加载的组件.png)]

有了自动配置类,免去了我们手动编写配置注入功能组件等的工作;

​ SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,classLoader);

==Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作;==以前我们需要自己配置的东西,自动配置类都帮我们;

J2EE的整体整合解决方案和自动配置都在spring-boot-autoconfigure-2.0.5.RELEASE.jar;

5、使用Spring Initializer快速创建Spring Boot项目

1、IDEA:使用 Spring Initializer快速创建项目

IDE都支持使用Spring的项目创建向导快速创建一个Spring Boot项目;

选择我们需要的模块;向导会联网创建Spring Boot项目;

默认生成的Spring Boot项目;

  • 主程序已经生成好了,我们只需要我们自己的逻辑
  • resources文件夹中目录结构
    • static:保存所有的静态资源; js css images;
    • templates:保存所有的模板页面;(Spring Boot默认jar包使用嵌入式的Tomcat,默认不支持JSP页面);可以使用模板引擎(freemarker、thymeleaf);
    • application.properties:Spring Boot应用的配置文件;可以修改一些默认设置;

2、STS使用 Spring Starter Project快速创建项目

~~~~~~~~~~~~~~~~

1、配置文件

1、配置文件概述

  • SpringBoot使用一个全局的配置文件,配置文件名是固定的;
    • application.properties
    • application.yml

配置文件的作用:修改SpringBoot自动配置的默认值;SpringBoot在底层都给我们自动配置好;

YAML(YAML Ain’t Markup Language)

​ YAML A Markup Language:是一个标记语言

​ YAML isn’t Markup Language:不是一个标记语言;

标记语言:

​ 以前的配置文件;大多都使用的是 xxxx.xml文件;

​ YAML:以数据为中心,比json、xml等更适合做配置文件;

​ YAML:配置例子

server:
  port: 8081

​ XML:

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

2、YAML语法:

1、基本语法

k:(空格)v:表示一对键值对(空格必须有);

空格的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级的

server:
    port: 8081
    path: /hello

属性和值也是大小写敏感;

2、值的写法

字面量:普通的值(数字,字符串,布尔)

​ k: v:字面直接来写;

​ 字符串默认不用加上单引号或者双引号;

​ “”:双引号;不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思

​ name: “zhangsan \n lisi”:输出;zhangsan 换行 lisi

​ ‘’:单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据

​ name: ‘zhangsan \n lisi’:输出;zhangsan \n lisi

对象、Map(属性和值)(键值对):

​ k: v:在下一行来写对象的属性和值的关系;注意缩进

​ 对象还是k: v的方式

friends:
		lastName: zhangsan
		age: 20

行内写法:

friends: {lastName: zhangsan,age: 18}

数组(List、Set):

用- 值表示数组中的一个元素

pets:
 - cat
 - dog
 - pig

行内写法

pets: [cat,dog,pig]

3、配置文件值注入

配置文件

person:
    lastName: hello
    age: 18
    boss: false
    birth: 2017/12/12
    maps: {k1: v1,k2: 12}
    lists:
      - lisi
      - zhaoliu
    dog:
      name: 小狗
      age: 12

javaBean:

/**
 * 将配置文件中配置的每一个属性的值,映射到这个组件中
 * @ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
 *      prefix = "person":配置文件中哪个下面的所有属性进行一一映射
 *
 * 只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能;
 *
 */
@Component
@ConfigurationProperties(prefix = "person")
public class Person {

    private String lastName;
    private Integer age;
    private Boolean boss;
    private Date birth;

    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;

我们可以导入配置文件处理器,以后编写配置就有提示了

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

1、properties配置文件在idea中默认utf-8可能会乱码

调整

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P65CBI2r-1605780851788)(./image/1.properties配置文件在idea中设置.png)]

2、@Value获取值和@ConfigurationProperties获取值比较

@ConfigurationProperties @Value
功能 批量注入配置文件中的属性 一个个指定
松散绑定(松散语法) 支持 不支持
SpEL 不支持 支持
JSR303数据校验 支持 不支持
复杂类型封装 支持 不支持

配置文件yml还是properties他们都能获取到值;

如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value;

如果说,我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties;

3、配置文件注入值数据校验

@Component
@ConfigurationProperties(prefix = "person")
@Validated
public class Person {

    /**
     * <bean class="Person">
     *      <property name="lastName" value="字面量/${key}从环境变量、配置文件中获取值/#{SpEL}"></property>
     * <bean/>
     */

   //lastName必须是邮箱格式
    @Email
    //@Value("${person.last-name}")
    private String lastName;
    //@Value("#{11*2}")
    private Integer age;
    //@Value("true")
    private Boolean boss;

    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;

4、@PropertySource&@ImportResource&@Bean

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

/**
 * 将配置文件中配置的每一个属性的值,映射到这个组件中
 * @ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
 *      prefix = "person":配置文件中哪个下面的所有属性进行一一映射
 *
 * 只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能;
 *  @ConfigurationProperties(prefix = "person")默认从全局配置文件中获取值;
 *
 */
@PropertySource(value = {"classpath:person.properties"})
@Component
@ConfigurationProperties(prefix = "person")
//@Validated
public class Person {

    /**
     * <bean class="Person">
     *      <property name="lastName" value="字面量/${key}从环境变量、配置文件中获取值/#{SpEL}"></property>
     * <bean/>
     */

   //lastName必须是邮箱格式
   // @Email
    //@Value("${person.last-name}")
    private String lastName;
    //@Value("#{11*2}")
    private Integer age;
    //@Value("true")
    private Boolean boss;

@ImportResource:导入Spring的配置文件,让配置文件里面的内容生效;

Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别;

想让Spring的配置文件生效,加载进来;@ImportResource标注在一个配置类上

@ImportResource(locations = {"classpath:beans.xml"})
导入Spring的配置文件让其生效

不来编写Spring的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id="helloService" class="com.atguigu.springboot.service.HelloService"></bean>
</beans>

SpringBoot推荐给容器中添加组件的方式;推荐使用全注解的方式

1、配置类**@Configuration**------>Spring配置文件

2、使用**@Bean**给容器中添加组件

/**
 * @Configuration:指明当前类是一个配置类;就是来替代之前的Spring配置文件
 *
 * 在配置文件中用<bean><bean/>标签添加组件
 *
 */
@Configuration
public class MyAppConfig {

    //将方法的返回值添加到容器中;容器中这个组件默认的id就是方法名
    @Bean
    public HelloService helloService02(){
        System.out.println("配置类@Bean给容器中添加组件了...");
        return new HelloService();
    }
}

##4、配置文件占位符

1、随机数

${random.value}、${random.int}、${random.long}
${random.int(10)}、${random.int[1024,65536]}

2、占位符获取之前配置的值,如果没有可以是用:指定默认值

person.last-name=张三${random.uuid}
person.age=${random.int}
person.birth=2017/12/15
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=${person.hello:hello}_dog
person.dog.age=15

5、Profile

1、多Profile文件

我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml

默认使用application.properties的配置;

2、yml支持多文档块方式

server:
  port: 8081
spring:
  profiles:
    active: prod

---
server:
  port: 8083
spring:
  profiles: dev


---

server:
  port: 8084
spring:
  profiles: prod  #指定属于哪个环境

3、**指定profile

​ 1、在配置文件中指定 spring.profiles.active=dev

​ 2、命令行:

​ java -jar spring-boot-02-config-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev;

​ 可以直接在测试的时候,配置传入命令行参数

​ 3、虚拟机参数;

​ -Dspring.profiles.active=dev

6、配置文件加载位置

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

–file:./config/

–file:./

–classpath:/config/

–classpath:/

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

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

我们还可以通过spring.config.location来改变默认的配置文件位置

项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;指定配置文件和默认加载的这些配置文件共同起作用形成互补配置;

java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --spring.config.location=G:/application.properties

7、外部配置加载顺序

SpringBoot也可以从以下位置加载配置; 优先级从高到低;高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置

1.命令行参数

所有的配置都可以在命令行上进行指定

java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --server.port=8087 --server.context-path=/abc

多个配置用空格分开; --配置项=值

2.来自java:comp/env的JNDI属性

3.Java系统属性(System.getProperties())

4.操作系统环境变量

5.RandomValuePropertySource配置的random.*属性值

由jar包外向jar包内进行寻找;

优先加载带profile

6.jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件

7.jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件

再来加载不带profile

8.jar包外部的application.properties或application.yml(不带spring.profile)配置文件

9.jar包内部的application.properties或application.yml(不带spring.profile)配置文件

aaa@qq.com注解类上的@PropertySource

11.通过SpringApplication.setDefaultProperties指定的默认属性

所有支持的配置加载来源;

参考官方文档

~~~~~~~~~~~~~~~~

2、SpringBoot自动配置原理

配置文件到底能写什么?怎么写?自动配置原理

配置文件配置的属性参照

自动配置原理:

  1. springboot启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfiguration

  2. @EnableAutoConfiguration的作用

  • 利用AutoConfigurationImportSelector类给容器中导入了哪些内容
AutoConfigurationImportSelector.class 的方法
    
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    
        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;
    }
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
    }
  • 可以查看selectImports()方法
public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            //获取候选的配置
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return StringUtils.toStringArray(configurations);
        }
    }
  • List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
SpringFactoriesLoader.loadFactoryNames()方法
	- 扫描所有jar包类路径下的 META-INF/spring.factories
	- 把扫描到的这些文件的内容包装成properties对象
	- 从properties对象中获取EnableAutoConfiguration.class类名对应的值,然后把它们添加到容器中

将 类路径下 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中

 ~~~properties
 # Auto Configure
 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
 org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
 org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
 org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
 org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
 org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
 org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
 org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
 org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
 org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
 org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
 org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
 org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
 org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
 org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
 org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
 org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
 org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
 org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
 org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
 org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
 org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
 org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
 org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
 org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
 org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
 org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
 org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
 org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
 org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
 org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
 org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
 org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
 org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
 org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
 org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
 org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
 org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
 org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
 org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
 org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
 org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
 org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
 org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
 org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
 org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
 org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
 org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
 org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
 org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
 org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
 org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
 org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
 org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
 org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
 org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
 org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
 org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
 org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
 org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
 org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
 org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
 org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration,\
 org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
 org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
 org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
 org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
 org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
 org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
 org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
 org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
 org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
 org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
 org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
 org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
 org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
 org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
 org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
 org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
 org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
 org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
 org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
 org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
 org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
 org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration
 每一个这样的xxxAutoConfiguration类都是容器中的一个组件,都会被加到容器中;用他们来做自动配置;
  1. 每一个自动配置类进行自动配置功能
  2. 以**HttpEncodingAutoConfiguration(HTTP编码自动配置)**为例解释自动配置原理:
@Configuration //表示这是一个自动配置类,类似于之前的配置文件,也可以给容器中添加组件
@EnableConfigurationProperties({HttpEncodingProperties.class}) //启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定;
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
//spring底层的@Condition注解,根据不同的条件,如果满足了指定的条件,整个配置文件中的配置就会生效;判断当前应用是不是web应用,如果是,当前配置类生效
@ConditionalOnClass({CharacterEncodingFilter.class}) //判断当前项目有没有这个类
@ConditionalOnProperty( 
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)//判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;

private final HttpEncodingProperties properties; 已经属性文件进行了映射

 public class HttpEncodingAutoConfiguration {
 //他已经和SpringBoot的配置文件映射了
 private final HttpEncodingProperties properties;
     //只有一个有参构造器的情况下,参数的值就会从容器中拿
     public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
     this.properties = properties;
     }
     @Bean //给容器中添加一个组件,这个组件的某些值需要从properties中获取
     @ConditionalOnMissingBean(CharacterEncodingFilter.class) //判断容器没有这个组件
     public CharacterEncodingFilter characterEncodingFilter() {
         CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
         filter.setEncoding(this.properties.getCharset().name());
         filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
         filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
         return filter;
 }
     -------------------------------------------
 @ConfigurationProperties(
    prefix = "spring.http.encoding"
)
public class HttpEncodingProperties {
    public static final Charset DEFAULT_CHARSET;
    private Charset charset;
    private Boolean force;
    private Boolean forceRequest;
    private Boolean forceResponse;
    private Map<Locale, Charset> mapping;

根据当前不同的条件判断,决定这个配置类是否生效?

一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取 的,这些类里面的每一个属性又是和配置文件绑定的;

  1. 所有在配置文件中能配置的属性都是在xxxProperties中封装的;配置文件能配置什么可以参照这个功能对应的Properties类
@ConfigurationProperties(prefix = "spring.http.encoding") //从配置文件中获取指定的值和bean的属
性进行绑定
public class HttpEncodingProperties {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF‐8");

精髓:

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

2)、我们看我们需要的功能有没有SpringBoot默认写好的自动配置类;

3)、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)

4)、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这 些属性的值;

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

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

@Conditional注解

  1. @Conditional派生注解(spring注解版原生的@Conditional作用)
    • 作用:必须是@Conditional指定的条件成立,才能给容器中添加组件,配置类里面的内容才生效
@Conditional扩展注解 作用(判断是否满足当前指定条件) 
@ConditionalOnJava 系统的java版本是否符合要求 
@ConditionalOnBean 容器中存在指定Bean; 
@ConditionalOnMissingBean 容器中不存在指定Bean; 
@ConditionalOnExpression 满足SpEL表达式指定 
@ConditionalOnClass 系统中有指定的类 
@ConditionalOnMissingClass 系统中没有指定的类 
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean 
@ConditionalOnProperty 系统中指定的属性是否有指定的值 
@ConditionalOnResource 类路径下是否存在指定资源文件 
@ConditionalOnWebApplication 当前是web环境 
@ConditionalOnNotWebApplication 当前不是web环境 
@ConditionalOnJndi JNDI存在指定项

自动配置类需要在一定的条件下才能生效;

  • 如何看自动配置类生效了
  • 可以通过在配置文件(application.properties)中配置debug=true,在console中来查看自动配置类是否启用

~~~~~~~~~~~~~~~~

3、SpringBoot和日志

1、日志框架

市面上的日志框架:JUL,JCL,Jboss-logging,logback,log4j,log4j2,slf4j

日志门面 (日志的抽象层) 日志实现
JCL(Jakarta Commons Logging) SLF4j(Simple Logging Facade for Java) jboss-logging Log4j JUL(java.util.logging) Log4j2 Logback

左边选一个门面(抽象层)、右边选一个实现

日志门面:SLF4J

日志实现:Logback

SpringBoot:底层是spring框架,spring框架默认是用JCL

springboot选用SLF4J和logback

2、SLF4J的使用

1、如何在系统中使用SLF4Jhttps://www.slf4j.org

以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法; 给系统里面导入slf4j的jar和 logback的实现jar

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    logger.info("Hello World"); 
    }
}

图示:

SpringBoot框架入门及使用教程(微服务学习笔记)

每一个日志的实现框架都有自己的配置文件。使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文 件;

2、遗留问题

a(slf4j+logback): Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx 多框架使用的日志不相同

统一日志记录,统一使用slf4j进行输出

SpringBoot框架入门及使用教程(微服务学习笔记)

解决办法:jcl-over-slf4j.jar(真正的实现slf4j-api.jar)排除并替换其他日志框架

如何让系统中所以的日志输出都统一到slf4j:

  1. 将系统中其他日志框架排除掉;
  2. 用中间包来替换原有的日志框架;
  3. 导入slf4j其他的实现

3、SpringBoot日志关系

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

SpringBoot使用它来做日志:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐logging</artifactId>
</dependency>

底层依赖关系:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eSbm0cWg-1605780851794)(./image/3.日志.png)]

总结:

  1. SpringBoot底层也是使用slf4j和logback的方式进行日志记录

  2. SpringBoot也把其他的日志都替换成了slf4j

  3. 中间的替换包

  4. 如果我们要引入其他的框架,一定要把这个框架的默认日志依赖移除掉

    1. 比如Spring框架用的commons-logging:
    dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring‐core</artifactId>
        <exclusions>
            <exclusion>
                <groupId>commons‐logging</groupId>
                <artifactId>commons‐logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    

    SpringBoot能自动适配所以的日志,而且底层使用sjf4j+logback的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉即可;

4、日志使用

    1. 默认配置

      SpringBoot默认帮我们配置好了日志;

      //记录器
      Logger logger = LoggerFactory.getLogger(getClass());
      @Test
      public void contextLoads() {
      //System.out.println();
      //日志的级别;
      //由低到高 trace<debug<info<warn<error
      //可以调整输出的日志级别;日志就只会在这个级别以以后的高级别生效
      logger.trace("这是trace日志...");
      logger.debug("这是debug日志...");
      //SpringBoot默认给我们使用的是info级别的,没有指定级别的就用SpringBoot默认规定的级别;root
      级别
      logger.info("这是info日志...");
      logger.warn("这是warn日志...");
      logger.error("这是error日志...");
      }
      
      
      日志输出格式:
      %d表示日期时间,
      %thread表示线程名,
      %5level:级别从左显示5个字符宽度
      %logger{50} 表示logger名字最长50个字符,否则按照句点分割。
      %msg:日志消息,x
      %n是换行符
      ‐‐>
      %d{yyyy‐MM‐dd HH:mm:ss.SSS} [%thread] %5level %logger{50}%msg%n
      

      SpringBoot修改日志的默认配置

      logging.level.cn.starfish=trace
      #logging.path=
      # 不指定路径在当前项目下生成springboot.log日志
      # 可以指定完整的路径;
      #logging.file=G:/springboot.log
      # 在当前磁盘的根路径下创建spring文件夹和里面的log文件夹;使用 spring.log 作为默认文件
      logging.path=/spring/log
      # 在控制台输出的日志的格式
      logging.pattern.console=%d{yyyy‐MM‐dd} [%thread] %5level %logger{50}%msg%n
      # 指定文件中日志输出的格式
      logging.pattern.file=%d{yyyy‐MM‐dd} === [%thread] === %5level === %logger{50} ==== %msg%n
      
      Logging.file Logging.path example Description
      (none) (none) 只在控制台输出
      指定文件 (none) My.log 输出日志到my。log
      (none) 指定目录 /var/log 输出到指定目录的spring。log文件中
      1. 指定配置

        给类路径下放上每个日志框架自己的配置文件即可;SpringBootj就不使用其他默认配置的了

        logging system
        logback logback-spring.groovy , logback.xml or logback.groovy
        Log4j2 log4j2-spring.xml or log4j2.xml
        Jdk(java.util.logging) logging.properties
        • logback.xml直接就被日志框架识别
        • logback-spring.xml:日志框架就不直接加载日志的配置项,由SpringBoot解析日志配置,可以使用SpringBoot
        <springProfile name="staging">
        <!‐‐ configuration to be enabled when the "staging" profile is active ‐‐>
        可以指定某段配置只在某个环境下生效
        </springProfile>
        

5、切换日志框架

可以按照slf4j的日志配图,进行相关的切换

  1. slf4j+log4j的方式:
<dendency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐web</artifactId>
    <exclusions>
        <exclusion>
        <artifactId>logback‐classic</artifactId>
        <groupId>ch.qos.logback</groupId>
        </exclusion>
    <exclusion>
        <artifactId>log4j‐over‐slf4j</artifactId>
        <groupId>org.slf4j</groupId>
        </exclusion>
    </exclusions>
</dendency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j‐log4j12</artifactId>
</dependency>
  1. 切换为log4j2:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring‐boot‐starter‐logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐log4j2</artifactId>
</dependency>

~~~~~~~~~~~~~~~~

4、web开发

1、简介

使用SpringBoot;

1)、创建SpringBoot应用,选中我们需要的模块;

2)、SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来

3)、自己编写业务代码;

自动配置原理?

这个场景SpringBoot帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?xxxx

xxxxAutoConfiguration:帮我们给容器中自动配置组件;
xxxxProperties:配置类来封装配置文件的内容;

2、SpringBoot对静态资源的映射关系

@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
    public class ResourceProperties implements ResourceLoaderAware {
//可以设置和静态资源有关的参数,缓存时间等
        WebMvcAuotConfiguration:
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
                return;
            }
            Integer cachePeriod = this.resourceProperties.getCachePeriod();
            if (!registry.hasMappingForPattern("/webjars/**")) {
                customizeResourceHandlerRegistration(
                        registry.addResourceHandler("/webjars/**")
                                .addResourceLocations(
                                        "classpath:/META‐INF/resources/webjars/")
                                .setCachePeriod(cachePeriod));
            }
            String staticPathPattern = this.mvcProperties.getStaticPathPattern();
//静态资源文件夹映射
            if (!registry.hasMappingForPattern(staticPathPattern)) {
                customizeResourceHandlerRegistration(
                        registry.addResourceHandler(staticPathPattern)
                                .addResourceLocations(
                                        this.resourceProperties.getStaticLocations())
                                .setCachePeriod(cachePeriod));
            }
        }
        //配置欢迎页映射
        @Bean
        public WelcomePageHandlerMapping welcomePageHandlerMapping(
                ResourceProperties resourceProperties) {
            return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(),
                    this.mvcProperties.getStaticPathPattern());
        }
        //配置喜欢的图标
        @Configuration
        @ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
        public static class FaviconConfiguration {
            private final ResourceProperties resourceProperties;
            public FaviconConfiguration(ResourceProperties resourceProperties) {
                this.resourceProperties = resourceProperties;
            }
            @Bean
            public SimpleUrlHandlerMapping faviconHandlerMapping() {
                SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
                mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
////所有 **/favicon.ico
                mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",
                        faviconRequestHandler()));
                return mapping;
            }
            @Bean
            public ResourceHttpRequestHandler faviconRequestHandler() {
                ResourceHttpRequestHandler requestHandler = new
                        ResourceHttpRequestHandler();
                requestHandler
                        .setLocations(this.resourceProperties.getFaviconLocations());
                return requestHandler;
            }
        }
  1. 所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找资源;

    webjars:以(maven坐标)或jar包的方式引入静态资源;

    <!‐‐引入jquery‐webjar‐‐>在访问的时候只需要写webjars下面资源的名称即可
    <de<dendency>
        <groupId>org.webjars</groupId>
        <artifactId>jquery</artifactId>
        <version>3.3.1</version>
    </d</dendency>
    
  2. “/**”访问当前项目的任何资源。(静态资源的文件夹)

    • 以下是能存放静态资源文件的文件夹路径
    "classpath:/META‐INF/resources/",
    "classpath:/resources/",
    "classpath:/static/",
    "classpath:/public/"
    "/":当前项目的根路径
    
    • localhost:8080/abc === 去静态资源文件夹里面找abc
  3. (欢迎页):静态资源文件夹下的所有index.html页面;被“/**”映射;

    • localhost;8080 默认访问 index.html
  4. (图标):所有的**/favicon.ico都是在静态资源文件夹下找;

  5. 可以在application.properties通过spring.resources.static-loactions属性修改静态资源路径

3、模板引擎

JSP、Velocity、Freemarker、Thymeleaf

SpringBoot推荐的Thymeleaf; 语法更简单,功能更强大;

1、 引入thymeleaf

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐thymeleaf</artifactId>
</dependency>
<!‐‐切换版本‐‐>
<properties>
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
<thymeleaf‐layout‐dialect.version>2.2.2</thymeleaf‐layout‐dialect.version>
</properties>
  • 注意事项: 布局功能的支持程序 thymeleaf3主程序需layout2以上版本
    thymeleaf2需layout1

2、thymeleaf的使用

@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING = Charset.forName("UTF‐8");
private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf("text/html");
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";

只要我们把HTML页面放在classpath:/templates/,thymeleaf就能自动渲染;

3、thymeleaf的语法

  • 使用thymeleaf 需要在HTML中导入名称空间
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
  • 使用thymeleaf语法
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF‐8">
<title>Title</title>
</head>
<body>
<h1>成功!</h1>
<!‐‐th:text 将div里面的文本内容设置为 ‐‐>
<!--会将${hello}的值覆盖div属性的值-->
<div th:text="${hello}">这是显示欢迎信息</div>
</body>
</html>
  • 语法规则
    • th:text:改变当前元素里面的文本内容
  • 表达式(参照thymeleaf文档)
Simple expressions:(表达式语法)
Variable Expressions: ${...}:获取变量值;OGNL;
    1)、获取对象的属性、调用方法
    2)、使用内置的基本对象:
        #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.
    ${session.foo}
    3)、内置的一些工具对象:
        #execInfo : information about the template being processed.
        #messages : methods for obtaining externalized messages inside variables expressions, in 	the same way as they would be obtained using #{…} syntax.
        #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.
        #ids : methods for dealing with id attributes that might be repeated (for example, as a 	result of an iteration).
Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样;
    补充:配合 th:object="${session.user}:
    <div th:object="${session.user}">
    <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
    <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
    <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
    </div>
Message Expressions: #{...}:获取国际化内容
Link URL Expressions: @{...}:定义URL;
    @{/order/process(execId=${execId},execType='FAST')}
Fragment Expressions: ~{...}:片段引用表达式
<div th:insert="~{commons :: main}">...</div>
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: + , ‐ , * , / , %
还有比较运算,条件运算(支持三元运算符),特殊操作(_)

4、 SpringMVC自动配置

[https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/reference/htmlsingle/#boot-features-developing- web-applications](https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/reference/htmlsingle/#boot-features-developing- web-applications)

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

1、Spring MVC auto-configuration

Springboot 自动配置好了springMVC

以下是springboot对springMVC的自动配置

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
    • 自动配置了ViewResolver(视图解析器:根据方法的返回值的到视图对象(View),视图对象·决定如何渲染(重定向?转发?))
    • ContentNegotiatingViewResolver:组合所以的视图解析器
    • 如何定制:我们可以自己给容器中添加一个视图解析器,自动的将其组合进来
  • Support for serving static resources, including support for WebJars (see below).
    • 静态资源文件夹路径webjars
  • Automatic registration of Converter, GenericConverter, Formatter beans.
    • Converter:转换器; public String hello(User user):类型转换使用Converter
    • Formatter:格式化器;2017.12.17===Date;
@Bean 
@ConditionalOnProperty(prefix = “spring.mvc”, name = “date‐format”)//在文件中配置日期格 
式化的规则 
public Formatter dateFormatter() { 
return new DateFormatter(this.mvcProperties.getDateFormat());//日期格式化组件 
}

自己添加的格式化器,我们只需要放在容器中即可

  • Support for HttpMessageConverters (see below).
    • HttpMessageConverter:SpringMVC用来转换Http请求和响应的;User—Json
    • HttpMessageConverters 是从容器中确定;获取所有的HttpMessageConverter;

自己给容器中添加HttpMessageConverter,只需要将自己的组件注册容器中 (@Bean,@Component)

  • Automatic registration of MessageCodesResolver (see below). 定义错误代码生成规则
  • Static index.html support.
  • Custom Favicon support (see below).
  • Automatic use of a ConfigurableWebBindingInitializer bean (see below).

我们可以配置一个ConfigurableWebBindingInitializer来替换默认的;(添加到容器)

ConfigurableWebBindingInitializer的作用:
初始化WebDataBinder
请求数据====JavaBean

2、扩展SpringMVC

原有xml配置文件的方式:

<mvc:view‐controller path="/hello" view‐name="success"/>
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/hello"/>
        <bean></bean>
    </mvc:interceptor>
</mvc:interceptors>

Springboot现有方式:编写一个配置类(@Configuration),是WebMvcConfigurerAdapter类型;不能标注@EnableWebMvc

既保留了所有的自动配置,也能用我们扩展的配置

@Configuration
public class MyMvcConfigure implements WebMvcConfigurer {
		
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/index").setViewName("success");
    }
}

原理:

  1. WebMvcAutoConfiguration是springmvc的自动配置类‘
  2. 在做其他的自动配合的时会导入;@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
    • EnableWebMvcConfiguration继承的父类DelegatingWebMvcConfiguration
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

    public DelegatingWebMvcConfiguration() {
    }

    @Autowired(
        required = false
    )
    //从容器中获取所有的WebMvcConfigurer并赋值到此类的成员变量上
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);
        }

    }
    //一个参考实现:所有的WebMvcConfigurer相关配置都一起调用
    protected void addInterceptors(InterceptorRegistry registry) {
        this.configurers.addInterceptors(registry);
    }
  1. 容器中所有的WebMvcConfigurer都会一起起作用
  2. 我们的配置类也会被调用

效果:springmvc的自动配置和我们自己的扩展配置的都会生效

spring:
  mvc:
    view:
      suffix: .jsp
      prefix: /WEB-INF/

3、@EnableWebMvc全面接管SpringMVC

@EnableWebMvc: 全部接管springMVC,所有配置都是我们自己配

  • 使用 在我们的配置类里加上此注解
@EnableWebMvc
@Configuration
public class MyMvcConfigure implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/index").setViewName("success");
    }
}

原理:添加@EnableWebMvc注解后自动配置失效

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

    public DelegatingWebMvcConfiguration() {
    }
@Configuration
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
//当容器中没有这个组件的时候,自动配置类才生效
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {
  1. @EnableWebMvc将WebMvcConfigurationSupport组件导入进来
  2. 导入的WebMvcConfigurationSupport只是springMVC的基本功能(不包含视图解析器,拦截器等)

4、注意事项:Spring5.0和SpringBoot2.0中废弃了WebMvcConfigurerAdapter

  • 解决方案
    • 1 、直接实现WebMvcConfigurer (官方推荐)
    • 2 、直接继承WebMvcConfigurationSupport
    • 在实现WebMvcConfigurationSupport的时候自己在application.properties配置的视图映射路径会失效

5、 如何修改SpringBoot的默认配置

模式:

  1. springboot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean @Component)如果有就用用户配置的,如果没有,才自动配置;如果有些组件有多个,可以将用户配置的和自动配置的组合起来使用;
  2. 在springboot中有很多xxxConfigurer帮助我们进行扩展配置
  3. 在springboot中会有很多的xxxCustomizer帮助我们进行配置

6、RestfulCRUD

1、 默认访问首页

@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
    //方式一
    /*aaa@qq.com
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("login");
        registry.addViewController("/index.html").setViewName("login");
    }*/
    //方式二
    @Bean
    public WebMvcConfigurerAdapter myWebMvcConfigurerAdapter() {
        return  new WebMvcConfigurerAdapter() {
            @Override
            public void addViewControllers(ViewControllerRegistry registry) {
                registry.addViewController("/").setViewName("login");
                registry.addViewController("/index.html").setViewName("login");
            }
        };
    }
}

2、国际化

  • 编写国际化配置文件
  • 使用ResourceBundleMessageSource管理国际化资源文件
  • 在页面使用fmt:message取出内容

步骤:

  1. 编写国际化配置文件,抽取页面需要显示的国际化消息

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cfJSKEzd-1605780851796)(./image/4.国际化–properties设置.png)]

  2. SpringBoot自动配置好了国际化资源文件的内容

  • 设置application.properties.basename属性
    • spring.messages.basename=i18n.login
@Configuration
@ConditionalOnMissingBean(
    value = {MessageSource.class},
    search = SearchStrategy.CURRENT
)
@AutoConfigureOrder(-2147483648)
@Conditional({MessageSourceAutoConfiguration.ResourceBundleCondition.class})
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {
    private static final Resource[] NO_RESOURCES = new Resource[0];

    public MessageSourceAutoConfiguration() {
    }

    @Bean
    @ConfigurationProperties(
        prefix = "spring.messages"
    )
    @Bean
    public MessageSource messageSource() {
        MessageSourceProperties properties = this.messageSourceProperties();
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        if (StringUtils.hasText(properties.getBasename())) {
            // 设置国际化资源文件的基础名(去掉语言国家代码) --> basenae = "messages"//我们的配置文件可以直接放在类路径下的messages.properties 也可以在application.properties文件中修改spring.messages.basename来修改国际化资源文件的存放位置
            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;
    }
}
  1. 去页面获取国际化的值
<!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, shrink-to-fit=no">
	<meta name="description" content="">
	<meta name="author" content="">
	<link rel="icon" href="../../../../favicon.ico">

	<title>Signin Template for Bootstrap</title>

	<!-- Bootstrap core CSS -->
	<link href="../../../../dist/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">

	<!-- Custom styles for this template -->
	<link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">
</head>

<body class="text-center">
<form class="form-signin">
	<img class="mb-4" src="https://getbootstrap.com/assets/brand/bootstrap-solid.svg" th:src="@{/asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72">
	<h1 th:text="#{login.tip}" class="h3 mb-3 font-weight-normal">Please sign in</h1>
	<label for="username" th:text="#{login.username}" class="sr-only">Username</label>
	<input type="email" id="username"class="form-control" placeholder="Username" required autofocus>
	<label for="inputPassword"  th:text="#{login.password}"  class="sr-only">Password</label>
	<input type="password" id="inputPassword" class="form-control" placeholder="Password" required>
	<div class="checkbox mb-3">
		<label>
			<input type="checkbox" value="remember-me"/> [[#{login.remember}]]
		</label>
	</div>
	<button class="btn btn-lg btn-primary btn-block" th:text="#{login.sign}" type="submit">Sign in</button>
	<p class="mt-5 mb-3 text-muted">&copy; 2017-2018</p>
</form>
</body>
</html>

效果:根据浏览器的语言的信息切换国际化

原理:

  • 国际化依赖Locale(区域信息对象);LocaleResolver(获取区域信息对象(Locale))

  • SpringBoot会根据请求头带来的区域信息获取Locale进行国际化

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-keKOKGEQ-1605780851797)(./image/4.国际化–请求头.png)]

  1. 点击链接切换国际化功能
<a class="btn btn-sm" th:href="@{/login.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/login.html(l='en_US')}">English</a>
  • 自定义 LocaleResolver对象实现LocaleResolver接口
public class MyLocaleResolver implements LocaleResolver {
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        String l = request.getParameter("l");
        Locale locale = request.getLocale();
        if (!StringUtils.isEmpty(l)) {
            String[] split = l.split("_");
            locale = new Locale(split[0], split[1]);

        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }
}
  • 将自定义的LocalResolver对象加入到容器中
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
    @Bean
    public LocaleResolver localeResolver() {
        return new MyLocaleResolver();
    }
}

3、 登录

开发期间模板引擎修改以后,要实时生效

  1. 禁用模板引擎的缓存
spring.thymeleaf.cache=false
  1. 页面修改完之后Ctrl+f9:重新编译

登录错误显示错误信息

  1. SpringMvc配置
@Controller
public class UserLoginController {

    @PostMapping("/user/login")
    public String login(@RequestParam(name="username") String username,
                        @RequestParam(name = "password") String password, Map<String,Object> map, HttpServletRequest request) {
        if ("admin".equals(username) && "123456".equals(password)) {
            request.getSession().setAttribute("loginUser",username);
            return "redirect:/main.html";
        } else {
            map.put("msg", "用户名或者密码错误");
            return "login";
        }
    }
}

  1. 静态页面设置
<p th:text="${msg}" style="color: red" th:if="${not #strings.isEmpty(msg)}" ></p>

4、 拦截器interceptor配置

  1. 自定义类实现HandlerInterceptor接口,重写preHandle方法
package com.example.demo.component;

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

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

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object loginUser = request.getSession().getAttribute("loginUser");
        if (loginUser == null) {
            request.setAttribute("msg","没有权限请先登录");
            request.getRequestDispatcher("/index.html").forward(request, response);
            return false;
        } else {
            return true;
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}
  1. 自定义类继承WebMvcConfigurerAdapter重写addInterceptors方法
  • SpringBoot2.0后此类作废,详情参照web开发–>4.SpringMVC自动配置–》4.注意事项
package com.example.demo.config;

import com.example.demo.component.LoginInterceptor;
import com.example.demo.component.MyLocaleResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
    public class MyMvcConfig extends WebMvcConfigurerAdapter {
    
    //方式二
    @Bean
    public WebMvcConfigurerAdapter myWebMvcConfigurerAdapter() {
        return  new WebMvcConfigurerAdapter() {
            @Override
            public void addViewControllers(ViewControllerRegistry registry) {
                registry.addViewController("/").setViewName("login");
                registry.addViewController("/index.html").setViewName("login");
                registry.addViewController("/main.html").setViewName("dashboard");
            }
        };
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //addInterceptor() 将自定义拦截器添加到容器中
        //addPathPatterns() 添加拦截路径
        //excludePathPatterns() 排除无需拦截的路径
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/index.html","/","/user/login");
    }
}

5、 CRUD-员工列表

实验要求:

  1. RestfulCRUD:CRUD满足rest风格;
  2. URI:/资源名称/资源标识 HTTP请求方式区分对资源的CRUD操作
普通CRUD(uri未区分操作) RestfulCRUD
查询 getEmp emp—GET
添加 addEmp?xxx emp—POST
修改 updateEmp?id=xxx&xxx=xx emp/{id}—PUT
删除 deleteEmp?id=1 emp/{id}—DELETE
  1. thymeleaf公共页面抽取
1、抽取公共片段
<div th:fragment="copy">
&copy; 2011 The Good Thymes Virtual Grocery
</div>
2、引入公共片段
<div th:insert="~{footer :: copy}"></div>
~{templatename::selector}:模板名::选择器
~{templatename::fragmentname}:模板名::片段名
3、默认效果:
insert的公共片段在div标签中
如果使用th:insert等属性进行引入,可以不用写~{}:
行内写法可以加上:[[~{}]];[(~{})]; 
  • 三种引入功能片段的th属性:
    • th:insert 将公共片段整个插入到引用元素中
    • th:include 将被引入的片段的内容包含进来
    • th:replace 将声明引入的元素替换为公共片段
模板名::片段名
footer.html  -->模板名称为footer
<footer th:fragment="copy">
&copy; 2011 The Good Thymes Virtual Grocery
</footer>

引入方式
<div th:insert="footer :: copy"></div>
引入片段的时候传入参数:
6)、CRUD-员工添加
添加页面
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
效果
<div>
<footer>
&copy; 2011 The Good Thymes Virtual Grocery
</footer>
</div>
<footer>
&copy; 2011 The Good Thymes Virtual Grocery
</footer>
<div>
&copy; 2011 The Good Thymes Virtual Grocery
</div>
============================================
模板名::选择器
footer.html -->模板名footer
<div id = "footer">
&copy; 2011 The Good Thymes Virtual Grocery
</div>
<div th:replace="footer ::#footer">
    
</div>

引入片段的时候传入参数

emp_list.html 引入片段
<div th:replace="commons/bar :: leftbar(activeUri='emps')"></div>
dashboard.html 引入片段
<div th:replace="commons/bar :: leftbar(activeUri='dashboard')"></div>
bar.html 被引入片段
<li class="nav-item">
    <!--根据引用传入的参数 改变样式-->
    <a class="nav-link active" href="#" th:href="@{/main.html}"
       th:class="${activeUri=='dashboard'?'nav-link active':'nav-link'}">
    <span data-feather="home"></span>
    Dashboard <span class="sr-only">(current)</span>
    </a>
</li>
<li class="nav-item">
 	<!--根据引用传入的参数 改变样式-->	
    <a class="nav-link" href="#" th:href="@{/emps}"
        th:class="${activeUri=='emps'?'nav-link active':'nav-link'}">>
    <span data-feather="users"></span>
    员工列表
    </a>
</li>

thymeleaf常用方法

<tr th:each="emp:${list}">
	<td th:text="${emp.id}"></td>
	<td th:text="${emp.lastName}"></td>
	<td>[[${emp.email}]]</td>
	<td th:text="${emp.gender==0?'':''}"></td>
	<td th:text="${emp.department.departmentName}"></td>
	<td th:text="${#dates.format(emp.birth,'yyyy-MM-dd HH:mm')}"></td>
</tr>
th:attr标签可以生成delUrl属性
<!--自定义属性的值-->
<button th:attr="aaa@qq.com{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteEmp">删除</button>
<form id="delEmpForm" action="" method="post">
				<input type="hidden" name="_method" value="delete">
			</form>
$(function () {
		$(".deleteEmp").click(function () {
            $("#delEmpForm").attr("action", $(this).attr("delUrl")).submit();
        });
    });

6、修改Form表单提交方式

  • SpringMVC配置HiddenHttpMethodFilter(SpringBoot自动配置了)
  • 页面创建一个post表单
  • 创建一个input项,name="_method",value值就是我们指定的请求方式
<input type="hidden" name="_method" value="put">

7、SpringBoot错误处理机制

1、 SpringBoot默认的错误处理机制

默认效果:

  1. 返回一个默认的错误页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qu0NOO1p-1605780851797)(./image/4.springboot默认错误页面.png)]

  1. 如果是其他客户端访问,返回json数据

原理:参照ErrorMvcAutoConfiguration:错误处理的自动配置

给容器添加了如下组件:

  • DefaultErrorAttributes:
帮我们在页面*享信息

//页面能获取到的信息
timestamp:时间戳

status:状态码

error:错误提示exception:异常对象

message:异常消息

errors:JSR303数据校验的错误都在这里
  • BasicErrorController:处理默认的error请求,根据请求头accept进行处理
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
    @RequestMapping(produces = "text/html") //产生HTML类型的页面,处理浏览器请求
	public ModelAndView errorHtml(HttpServletRequest request,
			HttpServletResponse response) {
		HttpStatus status = getStatus(request);
		Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
				request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
        
        //去哪个页面作为错误页面,包含页面地址和页面内容
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		return (modelAndView != null ? modelAndView : new ModelAndView("error", model));
	}

	@RequestMapping
	@ResponseBody //产生json数据,处理其他请求
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		Map<String, Object> body = getErrorAttributes(request,
				isIncludeStackTrace(request, MediaType.ALL));
		HttpStatus status = getStatus(request);
		return new ResponseEntity<Map<String, Object>>(body, status);
	}

  • ErrorPageCustomizer:
	@Value("${error.path:/error}")
	private String path = "/error";
//系统出现错误来到error请求进行处理,类似于(web.xml配置的错误处理机制)
  • DefaultErrorViewResolver:
@Override
	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
			Map<String, Object> model) {
		ModelAndView modelAndView = resolve(String.valueOf(status), model);
		if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
			modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
		}
		return modelAndView;
	}

	private ModelAndView resolve(String viewName, Map<String, Object> model) {
        //默认springboot去寻找页面 error/404
		String errorViewName = "error/" + viewName;
        //模板引擎可以解析,就用模板引擎解析
		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
				.getProvider(errorViewName, this.applicationContext);
		if (provider != null) {
            //模板引擎可用的基础上返回errorViewName对应的页面
			return new ModelAndView(errorViewName, model);
		}
        //模板引擎不可用的情况下,就在静态资源文件夹下errorViewName对应的页面
		return resolveResource(errorViewName, model);
	}

步骤:

  • 一旦页面中出现了4xx或5xx,ErrorPageCustomizer就会生效(定制错误的响应规则);-
  • 就会来到/error请求,就会被BasicErrorController处理
  • BasicErrorController处理完会返回响应的ModelAndView或者json数据
  • DefaultErrorViewResolver解析ModelAndView决定去哪个页面

2、如何定制错误响应

  • 如何定制错误的页面

    • 有模板引擎的情况下: error/状态码【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的error文件夹下】
    • 我们可以用4xx或者5xx作为错误文件的文件名来匹配这种类型的错误(优先匹配错误码.html)
    • 没有模板引擎的情况下(模板引擎未找到):会去类路径下的静态资源文件中获取
  • 如何定制错误的json数据

    1. 自定义异常处理消息:
    @ControllerAdvice
    public class MyExceptionHandler {
        @ResponseBody
        @ExceptionHandler(UserNotExistException.class)
        public Map<String,Object> handleException(Exception e){
            Map<String,Object> map = new HashMap<>();
            map.put("code","user.notexist");
            map.put("message",e.getMessage());
            return map;
        }
    }
    //没有自适应效果...
    
    1. 转发到/error进行自适应响应效果处理:(SpringBoot的BasicErrorController处理)
      @ExceptionHandler(UserNotExistException.class)
        public String handleException(Exception e, HttpServletRequest request){
            Map<String,Object> map = new HashMap<>();
    		//传入我们自己的错误状态码 4xx 5xx,否则就不会进入定制错误页面的解析流程
            request.setAttribute("javax.servlet.error.status_code",500);
            map.put("code","user.notexist");
            map.put("message",e.getMessage());
    		//转发到/error
            return "forward:/error";
        }
    //无法携带自定义数据
    
    1. 携带自定义数据:

      出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由

    getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法);

    • 完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;

    • 页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;SpringBoot容器中通过DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;

    自定义ErrorAttributes

    //给容器中加入我们自己定义的ErrorAttributes
    @Component
    public class MyErrorAttributes extends DefaultErrorAttributes {
        @Override
        public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
                                                      boolean includeStackTrace) {
            Map<String, Object> map = super.getErrorAttributes(requestAttributes,
                    includeStackTrace);
            map.put("company","abc");
            return map;
        }
    

    总结:自定义异常处理注意事项:

    1. 通过/error转发springboot默认的异常处理controller
    2. 传入我们自己的错误状态码 4xx 5xx(不然无法进入定制错误页面的解析流程)
     request.setAttribute("javax.servlet.error.status_code",500);
    
    1. 自定义ErrorAttributes将自定义数据携带出去,视图显示

8、配置嵌入式servlet容器

SpringBoot默认是用的嵌入式容器(Tomcat):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y9NgVzWA-1605780851798)(./image/4.springboot内置tomcat.png)]

1、如何定制和修改servlet容器的相关配置

  • 修改和server有关的配置
server.port=8081
server.context‐path=/crud
server.tomcat.uri‐encoding=UTF‐8
//通用的Servlet容器设置
server.xxx
//Tomcat的设置
server.tomcat.xxx
  • 编写一个EmbeddedServletContainerCustomizer实体加入到容器中:嵌入式的servlet容器的定制器,来修改配置
@Bean //一定要将这个定制器加入到容器中
    public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
        return new EmbeddedServletContainerCustomizer() {
            //定制嵌入式的Servlet容器相关的规则
            @Override
            public void customize(ConfigurableEmbeddedServletContainer container) {
                container.setPort(8083);
            }
        };
    }
  • 若同时配置了propertiesEmbeddedServletContainerCustomizer则前者会被覆盖掉

2、注册servlet的三大组件(servlet,listener,filter)

由于springboot默认是以jar包的方式启动嵌入式的servlet容器来启动springboot的web应用,没有web.xml文件

注册三大组件

  • ServletRegistrationBean
//自定义Servlet
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("Hello World");
    }
}
@Configuration
public class MyServletConfig {

    @Bean
    public ServletRegistrationBean myServletRegistrationBean() {
        return new ServletRegistrationBean(new MyServlet(), "/myServlet");
    }
}
  • ServletListenerRegistrationBean
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("项目被启动了");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("项目被关闭了");
    }
}
@Configuration
public class MyServletConfig {
    @Bean
    public ServletListenerRegistrationBean myListener() {
        ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean();
        bean.setListener(new MyListener());
        return bean;
    }
}
  • FilterRegistrationBean
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("filter执行了");
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {

    }
}
@Configuration
public class MyServletConfig {
    
    @Bean
    public FilterRegistrationBean myFilter() {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new MyFilter());
        bean.addUrlPatterns("/myServlet","/");
        return bean;

    }
}

springboot帮我们自动配置springMVC的时候,自动注册了springMVC的前端控制器:DispatcherServlet

@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
		@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public ServletRegistrationBean dispatcherServletRegistration(
				DispatcherServlet dispatcherServlet) {
			ServletRegistrationBean registration = new ServletRegistrationBean(
					dispatcherServlet, this.serverProperties.getServletMapping());
//默认拦截:/ 所有请求,包含静态资源,但不拦截jsp; /*会拦截jsp
//可以通过server.servletPath来修改springboot前端控制器默认拦截的请求路径
            registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
			registration.setLoadOnStartup(
					this.webMvcProperties.getServlet().getLoadOnStartup());
			if (this.multipartConfig != null) {
				registration.setMultipartConfig(this.multipartConfig);
			}
			return registration;
		}

3、springboot能不能支持其他的servlet容器

默认支持Tomcat(默认):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐web</artifactId>
    引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器;
</dependency>

切换为Jetty:

<!‐‐ 引入web模块 ‐‐>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐web</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>spring‐boot‐starter‐tomcat</artifactId>
            <groupId>org.springframework.boot</groupId>
        </exclusion>
    </exclusions>
</dependency>
<!‐‐引入其他的Servlet容器‐‐>
<dependency>
    <artifactId>spring‐boot‐starter‐jetty</artifactId>
    <groupId>org.springframework.boot</groupId>
</dependency>

切换为Undertow :

<!‐‐ 引入web模块 ‐‐>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐web</artifactId>
    <exclusions>
    <exclusion>
        <artifactId>spring‐boot‐starter‐tomcat</artifactId>
        <groupId>org.springframework.boot</groupId>
    </exclusion>
    </exclusions>
</dependency>
<!‐‐引入其他的Servlet容器‐‐>
<dependency>
    <artifactId>spring‐boot‐starter‐undertow</artifactId>
    <groupId>org.springframework.boot</groupId>
</dependency>

4、嵌入式servlet容器自动配置

EmbeddedServletContainerAutoConfiguration:嵌入式的Servlet容器自动配置?

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
//导入BeanPostProcessorsRegistrar:Spring注解版;给容器中导入一些组件
//导入了EmbeddedServletContainerCustomizerBeanPostProcessor:
//后置处理器:bean初始化前后(创建完对象,还没赋值赋值)执行初始化工作
public class EmbeddedServletContainerAutoConfiguration {
    @Configuration
    @ConditionalOnClass({ Servlet.class, Tomcat.class })//判断当前是否引入了Tomcat依赖;
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search =
            SearchStrategy.CURRENT)//判断当前容器没有用户自己定义EmbeddedServletContainerFactory:嵌入式的Servlet容器工厂;作用:创建嵌入式的Servlet容器
    public static class EmbeddedTomcat {
        @Bean
        public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory()
        {
            return new TomcatEmbeddedServletContainerFactory();
        }
    }
    /**
     * Nested configuration if Jetty is being used.
     */
    @Configuration
    @ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
            WebAppContext.class })
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search =
            SearchStrategy.CURRENT)
    public static class EmbeddedJetty {
        @Bean
        public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
            return new JettyEmbeddedServletContainerFactory();
        }
    }
    /**
     * Nested configuration if Undertow is being used.
     */
    @Configuration
    @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search =
            SearchStrategy.CURRENT)
    public static class EmbeddedUndertow {
        @Bean
        public UndertowEmbeddedServletContainerFactory
        undertowEmbeddedServletContainerFactory() {
            return new UndertowEmbeddedServletContainerFactory();
        }
    }

1)、EmbeddedServletContainerFactory(嵌入式Servlet容器工厂)

public interface EmbeddedServletContainerFactory {
//获取嵌入式的Servlet容器
EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers);
}

2)、EmbeddedServletContainer:(嵌入式的Servlet容器)

3)、以TomcatEmbeddedServletContainerFactory为例

 @Override
    public EmbeddedServletContainer getEmbeddedServletContainer(
            ServletContextInitializer... initializers) {
//创建一个Tomcat
        Tomcat tomcat = new Tomcat();
//配置Tomcat的基本环节
        File baseDir = (this.baseDirectory != null ? this.baseDirectory
                : createTempDir("tomcat"));
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        prepareContext(tomcat.getHost(), initializers);
//将配置好的Tomcat传入进去,返回一个EmbeddedServletContainer;并且启动Tomcat服务器
        return getTomcatEmbeddedServletContainer(tomcat);

我们对嵌入式容器的配置修改是怎么生效?

ServerProperties(也是一个定制处理器)、EmbeddedServletContainerCustomizer(定制处理器)

EmbeddedServletContainerCustomizer:定制器帮我们修改了Servlet容器的配置? 怎么修改的原理?

4)、容器中导入了EmbeddedServletContainerCustomizerBeanPostProcessor

 //初始化之前
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
//如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件
        if (bean instanceof ConfigurableEmbeddedServletContainer) {
//
            postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
        }
        return bean;
    }
    private void postProcessBeforeInitialization(
            ConfigurableEmbeddedServletContainer bean) {
//获取所有的定制器,调用每一个定制器的customize方法来给Servlet容器进行属性赋值;
        for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
            customizer.customize(bean);
        }
    }
    private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
        if (this.customizers == null) {
// Look up does not include the parent context
            this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
                    this.beanFactory
//从容器中获取所有这葛类型的组件:EmbeddedServletContainerCustomizer
//定制Servlet容器,给容器中可以添加一个EmbeddedServletContainerCustomizer类型的组件
                            .getBeansOfType(EmbeddedServletContainerCustomizer.class,
                                    false, false)
                            .values());
            Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
            this.customizers = Collections.unmodifiableList(this.customizers);
        }
        return this.customizers;
    }
ServerProperties也是定制器

步骤:

1)、SpringBoot根据导入的依赖情况,给容器中添加相应的

EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】

2)、容器中某个组件要创建对象就会惊动后置处理器; EmbeddedServletContainerCustomizerBeanPostProcessor;

只要是嵌入式的Servlet容器工厂,后置处理器就工作;

3)、后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer,调用定制器的定制方法

5、嵌入式Servlet容器启动原理

什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat; 获取嵌入式的Servlet容器工厂: 1)、SpringBoot应用启动运行run方法

2)、refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个 组 件 】 ; 如 果 是 web 应 用 创 建 AnnotationConfigEmbeddedWebApplicationContext, 否 则 : AnnotationConfigApplicationContext

3)、refresh(context);刷新刚才创建好的ioc容器;

ublic void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post‐processing of the bean factory in context subclasses.

4)、 onRefresh(); web的ioc容器重写了onRefresh方法

5)、webioc容器会创建嵌入式的Servlet容器;createEmbeddedServletContainer();

6)、获取嵌入式的Servlet容器工厂:

EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();

从ioc容器中获取EmbeddedServletContainerFactory 组件;TomcatEmbeddedServletContainerFactory创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置;

7)、使用容器工厂获取嵌入式的Servlet容器:this.embeddedServletContainer = containerFactory

.getEmbeddedServletContainer(getSelfInitializer());

8)、嵌入式的Servlet容器创建对象并启动Servlet容器;

先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来;

IOC容器启动创建嵌入式的Servlet容器

9、使用外置的Servlet容器

嵌入式Servlet容器:应用打成可执行的jar

  • 优点:简单、便携;

  • 缺点:默认不支持JSP、优化定制比较复杂(使用定制器【ServerProperties、自定义

EmbeddedServletContainerCustomizer】,自己编写嵌入式Servlet容器的创建工厂

外置的Servlet容器:外面安装Tomcat—以war的方式打包;

1)、步骤

  1. 必须创建一个war项目(利用idea创建好目录结构)
  2. 将内置Tomcat指定为provided
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐tomcat</artifactId>
    <scope>provided</scope>
</dependency>
  1. 必须修改一个SpringBootServletInitializer的子类,并调用configure方法
public class ServletInitializer extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//传入SpringBoot应用的主程序
        return application.sources(SpringBoot04WebJspApplication.class);
    }
}
  1. 启动服务器就可以使用了

2)、原理

  • jar包:执行springboot主类的main方法,启动IOC容器,创建嵌入式的Servlet容器
  • war包:启动服务器,服务器启动springboot应用,启动IOC容器

servlet3.0(Spring注解版):

8.2.4 Shared libraries / runtimes pluggability: 规则:

1)、服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例:

2)、ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为

javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名

3)、还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;

流程:

1)、启动Tomcat

2)、org\springframework\spring-web\4.3.14.RELEASE\spring-web-4.3.14.RELEASE.jar!\META- INF\services\javax.servlet.ServletContainerInitializer:

Spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer

3)、SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set>;为这些WebApplicationInitializer类型的类创建实例;

4)、每一个WebApplicationInitializer都调用自己的onStartup;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eciDP3Hr-1605780851799)(./image/4.WebApplicationInitializer实现.png)]

5)、相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法

6)、SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器

protected WebApplicationContext createRootApplicationContext(
            ServletContext servletContext) {
//1、创建SpringApplicationBuilder
        SpringApplicationBuilder builder = createSpringApplicationBuilder();
        StandardServletEnvironment environment = new StandardServletEnvironment();
        environment.initPropertySources(servletContext, null);
        builder.environment(environment);
        builder.main(getClass());
        ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
        if (parent != null) {
            this.logger.info("Root context already created (using as parent).");
            servletContext.setAttribute(
                    WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
            builder.initializers(new ParentContextApplicationContextInitializer(parent));
        }
        builder.initializers(
                new ServletContextApplicationContextInitializer(servletContext));
        builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
//调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来
        builder = configure(builder);
//使用builder创建一个Spring应用
        SpringApplication application = builder.build();
        if (application.getSources().isEmpty() && AnnotationUtils
                .findAnnotation(getClass(), Configuration.class) != null) {
            application.getSources().add(getClass());
        }
        Assert.state(!application.getSources().isEmpty(),
                "No SpringApplication sources have been defined. Either override the "
                        + "configure method or add an @Configuration annotation");
// Ensure error pages are registered
        if (this.registerErrorPageFilter) {
            application.getSources().add(ErrorPageFilterConfiguration.class);
        }
//启动Spring应用
        return run(application);
    }

7)、Spring的应用就启动并且创建IOC容器

 public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            analyzers = new FailureAnalyzers(context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
//刷新IOC容器
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            listeners.finished(context, null);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            return context;
        }
        catch (Throwable ex) {
            handleRunFailure(context, listeners, analyzers, ex);
            throw new IllegalStateException(ex);
        }
    }

启动Servlet容器,再启动SpringBoot应用

3)、核心

 根据Servlet3.0的标准服务器启动的时候,会加载SpringBootServletInitializer实现的实例,而在它的实例里面重写了configure方法,这个方法标识了主程序的位置,这样服务器启动的时候就会加载这个主程序

10、SpringBoot和jsp

1、使用外置的tomcat容器启动

1、创建web项目

  • 打包方式为war的形式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f44b3BnM-1605780851800)(./image/4.创建web项目.png)]

  • 修改webapp指向位置目录结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hq5JPdfT-1605780851801)(./image/4.%E4%BF%AE%E6%94%B9web%20app%E6%8C%87%E5%90%91%E7%9A%84%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84.png)]

  • 创建jsp存储路径和jsp

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tY22gLYt-1605780851802)(./image/4.创建jsp存储文件目录.png)]

  • 配置外置tomcat容器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WFreeFP2-1605780851802)(./image/4.添加外置tomcat容器.png)]

2、修改配置文件

spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

3、编写Controller测试

@Controller
public class JspController {

    @GetMapping("hello")
    public String jsp() {
        return "hello";
    }
}

2、使用内置的tomcat容器启动

1、创建web项目

  • Spring Initializer创建war打包方式的web项目,并在其基础上添加如下依赖
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
</dependency>
  • 打包方式为war的形式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oUguTGah-1605780851803)(/Users/mac/Documents/#SpringBoot/image/4.创建web项目.png)]

  • 修改webapp指向位置目录结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KQ80dEFR-1605780851804)(./image/4.修改web app指向的目录结构.png)]

  • 创建jsp存储路径和jsp

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QFyuPYXT-1605780851805)(./image/4.创建jsp存储文件目录.png)]

2、修改配置文件

spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

3、编写Controller测试

@Controller
public class JspController {

    @GetMapping("hello")
    public String jsp() {
        return "hello";
    }
}

~~~~~~~~~~~~~~~~

5、SpringBoot和Docker

1、简介

Docker是一个开源的应用容器引擎:

Docker支持将软件编译成一个镜像;然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使 用这个镜像

运行中的这个镜像称为容器,容器启动是非常快速的。

2、核心概念

  • docker主机(host):
  • docker客户端(client):
  • docker仓库:用来保存打包好的镜像
  • docker镜像:软件打包好的镜像,放在docker仓库中
  • docker容器:镜像启动后生产的实例,称为一个容器;容器是独立的一个或一组应用

使用步骤

  1. 安装Docker
  2. 去Docker仓库找到这个软件对应的镜像
  3. 使用docker这个镜像,这个镜像会生成一个docker容器
  4. 对容器的启动体质就是对软件的启动停止

3、安装docker

1)安装linux虚拟机

  1. VMWare、VirtualBox(安装);

  2. 导入虚拟机文件centos7-atguigu.ova;

  3. 双击启动linux虚拟机;使用 root/ 123456登陆

  4. 使用客户端连接linux服务器进行命令操作;

  5. 设置虚拟机网络;

    桥接网络=选好网卡==接入网线;

  6. 设置好网络以后使用命令重启虚拟机的网络

service network restart 
  1. 查看linux的ip地址
ip addr
  1. 使用客户端连接linux;

2)在Linux下安装docker

  1. 查看centos的版本(docker要求centos系统的内核版本高于3.10)
uname -r
  1. 升级软件包及内核(3.10版本以下进行升级)
yum update
  1. 安装docker
yum install docker

//查看版本号
docker -v
  1. 启动docker
systemctl start docker

//关闭docker
stop docker
  1. 将docker服务设为开机启动
systemctl enable docker

3)docker的常用镜像操作

docker search + 关键字(例如docker search mysql)//搜索

docker pull + 镜像:tag//下载,tag表示标签,多为软件的版本,默认是latest 

docker images  //查看所有本地镜像

docker rmi + 镜像名称 //删除镜像

https://hub.docker.com/

4)docker常用的容器操作

软件镜像(QQ安装程序)—-运行镜像—-产生一个容器(正在运行的软件,运行的QQ);

步骤:

1.搜索镜像
docker search tomcat
2.拉取镜像
docker pull tomcat
3.根据镜像启动容器
docker run --name mytomcat -d tomcat:latest
4.查看运行中的容器
docker ps 
5.停止运行中的容器
docker stop 容器ID 或者 docker stop容器名字
6. 查看所有的容器
docker ps -a
7. 停止容器
docker stop + 容器ID
8. 删除容器
docker rm 容器ID
9. 启动有个做了端口映射的tomcat
docker run -d -p 8888:8080 tomcat
-d:后台运行
-p:将主机的端口映射到容器的一个端口 主机端口:容器内部端口
10. 关闭Linux防火墙
service firewalld status:查看防火墙状态
service firewalld stop:关闭防火墙
11.查看容器日志
docker logs 容器名称/容器ID
12.进入容器内部 73f01be51ee8表示容器ID
docker exec -it 73f01be51ee8 /bin/bash
13.显示容器内文件和文件夹
aaa@qq.com:/usr/share/elasticsearch# ls -l
14.进入某一个文件夹
aaa@qq.com:/usr/share/elasticsearch# cd config/
15.编辑文件,由于docker下未安装vim/vi指令
aaa@qq.com:/usr/share/elasticsearch/config# vim elasticsearch.yml 
bash: vim: command not found
安装指令
	安装Vi:apt-get install vim,如果提示:Unable to locate package vim,则需要敲:apt-get update, 等更新完毕以后再敲命令: apt-get install vim
16.退出容器内部
exit

关于docker容器的操作,可以参照官方文档

一个镜像可以启动多个容器,而且每个容器都是独立的,互不干扰

1、安装MySQL

1.拉取镜像
docker pullmysql
2.启动mysql
[aaa@qq.com ~]# docker run ‐‐name mysql01 ‐e MYSQL_ROOT_PASSWORD=123456 ‐d mysql
3.查看错误日志
[aaa@qq.com ~]# docker logs 42f09819908b
注意事项:启动MySQL时:MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and
MYSQL_RANDOM_ROOT_PASSWORD;这个三个参数必须指定一个

2、docker中MySQL镜像的高级操作

docker run ‐‐name mysql03 ‐v /conf/mysql:/etc/mysql/conf.d ‐e MYSQL_ROOT_PASSWORD=123456
‐d mysql:tag
把主机的/conf/mysql文件夹挂载到 mysqldocker容器的/etc/mysql/conf.d文件夹里面
改mysql的配置文件就只需要把mysql配置文件放在自定义的文件夹下(/conf/mysql)
docker run ‐‐name some‐mysql ‐e MYSQL_ROOT_PASSWORD=123456 ‐d mysql:tag ‐‐character‐set‐
server=utf8mb4 ‐‐collation‐server=utf8mb4_unicode_ci
指定mysql的一些配置参数

5) docker镜像加速

$ docker pull registry.docker-cn.com/library/mysql

~~~~~~~~~~~~~~~~

6、SpringBoot与数据访问层

1、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>
spring:
    datasource:
    	username: root
   		password: 123456
    	url: jdbc:mysql://192.168.15.22:3306/jdbc
    	driver‐class‐name: com.mysql.jdbc.Driver

效果:

默认是用org.apache.tomcat.jdbc.pool.DataSource作为数据源;	

数据源的相关配置在DataSourceProperties

自动配置原理:

org.springframework.boot.autoconfigure.jdbc:

1、参考DataSourceConfiguration,根据配置创建数据源,默认使用Tomcat连接池;可以使用spring.datasource.type指定自定义的数据源类型;

2、SpringBoot默认可以支持:

org.apache.tomcat.jdbc.pool.DataSource、HikariDataSource、BasicDataSource、

3、自定义数据源类型

/**
* Generic DataSource configuration.
*/
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type")
static class Generic {
    @Bean
public DataSource dataSource(DataSourceProperties properties) {
    //使用DataSourceBuilder创建数据源,利用反射创建响应type的数据源,并且绑定相关属性
    return properties.initializeDataSourceBuilder().build();
 }
}

4 、 DataSourceInitializer:ApplicationListener;

作用:

1)、runSchemaScripts();运行建表语句; 

2)、runDataScripts();运行插入数据的sql语句; 
  • 默认只需要将文件命名为:(存放在classpath路径下,即可在项目启动时被加载)
schema‐*.sql(建表)、data‐*.sql(插入数据)
默认规则:schema.sql,schema‐all.sql;
  • 指定指特点位置;
spring:
  datasource:
    schema: 
		‐ classpath:department.sql
	initialization-mode: always

5、操作数据库:自动配置了JdbcTemplate操作数据库

@Controller
public class HelloController {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @GetMapping("list")
    @ResponseBody
    public Map<String, Object> list() {
        List<Map<String, Object>> maps = jdbcTemplate.queryForList("SELECT * FROM USERS ");
        return maps.get(0);
    }
}

2、整合Druid数据源

  1. 引入maven仓库
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.10</version>
</dependency>
  1. 设置properties相关配置
spring:
  datasource:
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://localhost:3306/springboot
    # 下面为连接池的补充设置,应用到上面所有数据源中
    initialSize: 5
    minIdle: 5
    maxActive: 20
    # 配置获取连接等待超时的时间
    maxWait: 60000
    # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
    timeBetweenEvictionRunsMillis: 60000 
    # 配置一个连接在池中最小生存的时间,单位是毫秒
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPrparedStatements: true
    # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectProperties: druid.stat.mergeSql=true,druid.stat.slowSqlMills=500
    logSlowSql: true
  1. 将Druid配置添加到容器中
@Configuration
public class DruidConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DruidDataSource druidDataSource() {
        return new DruidDataSource();
    }

    //配置Druid的监控
    //1、配置一个管理后台的Servlet
    @Bean
    public ServletRegistrationBean statViewServlet() {
        ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        Map<String, String> initParams = new HashMap<>();
        initParams.put("loginUsername", "admin");
        initParams.put("loginPassword", "123456");
        initParams.put("allow", "");//默认就是允许所有访问
        initParams.put("deny", "192.168.15.21");//不允许访问的设置
        bean.setInitParameters(initParams);
        return bean;
    }

    //2、配置一个web监控的filter
    @Bean
    public FilterRegistrationBean webStatFilter() {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());
        Map<String, String> initParams = new HashMap<>();
        initParams.put("exclusions", "*.js,*.css,/druid/*");
        bean.setInitParameters(initParams);
        bean.setUrlPatterns(Arrays.asList("/*"));
        return bean;
    }
}

3、整合Mybatis

<dependency>          		    
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>
<!--引入Druid数据源-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.10</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

将Druid配置添加到容器中 参照2.3 将Druid配置添加到容器中

1、注解版

//指定这是一个操作数据库的mapper
//或者使用MapperScan进行批量扫描,被扫描目录下所有的接口相当于自动添加了@Mapper注解
@Mapper
public interface DepartmentMapper {

    @Select("select * from department where id = #{id}")
    public Department getDepatById(Integer id);
    
    @Options(useGeneratedKeys = true,keyProperty = "id") //设置insert成功后返回主键ID
    @Insert("insert into department(departmentName) values(#{departmentName})")
    public int saveDepart(Department department);

    @Delete("delete from department where id = #{id}")
    public int deleteDepartById(Integer id);

    @Update("update department department_name = #{departmentName} where id = #{id}")
    public int updateDeprtById(Department department);
}

若想批量扫描接口为mybatis的mapper映射文件,可做如下配置

@SpringBootApplication
//com.example.demo.mapper下所有的接口相当于自动添加了@Mapper注解
//将接口扫描装备到容器中
//@MapperScan("com.example.demo.mapper")
public class DemoApplication {

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

支持匹配驼峰命名法:

  • 数据库字段 department_name 映射到entity字段上的departmentName
@org.springframework.context.annotation.Configuration
public class MyBatisConfig {
    @Bean
    public ConfigurationCustomizer configurationCustomizer(){
        return new ConfigurationCustomizer(){
            @Override
            public void customize(Configuration configuration) {
                configuration.setMapUnderscoreToCamelCase(true);
            }
        };
    }
}

2、配置文件的方式

  • mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration
    <!--支持驼峰命名的方式-->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>
  • mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.EmployeeMapper">
    <select id="findById" resultType="com.example.demo.bean.Employee">
    select * from employee where id = #{id}
    </select>

    <insert id="saveEmp">
        insert into employee(lastName,email,gender,d_id) values (#{lastName},#{email},#{gender},#{did})
    </insert>
</mapper>
  • Mapper
@Mapper
//或者使用MapperScan进行批量扫描
public interface EmployeeMapper {

    public Employee findById(Integer id);

    public int saveEmp(Employee employee);

}

4、整合JPA

1、SpringData介绍

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JHCSjiGk-1605780851806)(./image/6.jpa模型.png)]

2、整合SpringDataJPA

​ JPA ORM(Object Relation Mapping):

1)编写一个实体类(bean)和数据表进行映射,并配置好映射关系

//使用JPA注解配置映射关系
@Entity //告诉JPA这是一个实体类(和数据表映射的类)
@Table(name = "user")//@Table来指定和哪个数据表对应;如果省略默认表名就是userEntity;
public class UserEntity {
    @Id //这是一个主键
    @GeneratedValue(strategy = GenerationType.IDENTITY) //自动增长
    @Column(name = "user_id")
    private Integer id;
    @Column(name = "last_name") //这是和数据库表对应的一个列,若省略name属性则列名及为属性名
    private String lastName;
    @Column(name = "sex")
    private String sex;

    public Integer getId() {
        return id;
    }

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

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

2)编写一个Dao接口来操作实体类对应的数据表(Repository)

//继承JpaRepository来完成对数据库的操作
public interface UserRepostory extends JpaRepository<UserEntity,Integer> {
}

3)基本的配置JpaProeprties

spring:
  datasource:
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springboot
  jpa:
      properties:
        hibernate:
          format_sql: true
          show_sql: true
      hibernate:
        ddl-auto: update

4)测试

@RestController
public class UserController {

    @Autowired
    UserRepostory userRepostory;
    @GetMapping("/user")
    public UserEntity findById(Integer id) {
        return userRepostory.findOne(id);
    }
}

3、@Query注解自定义SQL

public interface UserRepostory extends JpaRepository<UserEntity,Integer> {

    @Query(value = "select * from user where sex = ?1",nativeQuery = true)
    public List<UserEntity> findList(String sex);
}
  • @Query是用来配置自定义SQL的注解,后面参数nativeQuery = true表明了使用原生的sql,如果不配置,默认是false

4、@Query配合@Modifying

  • 从名字上可以看到我们的@Query注解好像只是用来查询的,但是如果配合@Modifying注解一共使用,则可以完成数据的删除、添加、更新操作。
package com.example.demo.jpa;

import com.example.demo.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

public interface UserRepostory extends JpaRepository<UserEntity,Integer> {

    //根据用户名、性别删除一条数据
    @Modifying
    @Query(value = "delete  from user where last_name = ?1 and sex = ?2", nativeQuery = true)
    public boolean deleteByNameAndSex(String last_name, String sex);
}

  • 异常TranscationRequiredException,意思就是你当前的操作给你抛出了需要事务异常,SpringDataJPA自定义SQL时需要在对应的接口或者调用接口的地方添加事务注解**@Transactional**,来开启事务自动化管理。下面我们在UserJPA内添加**@Transactional**注解

5、抽取BaseRepository

package com.example.demo.base;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;

import java.io.Serializable;

//@NoRepositoryBean 这个注解如果配置在继承了JpaRepository接口以及其他SpringDataJpa内部的接口的子接口时,子接口不被作为一个Repository创建代理实现类。
@NoRepositoryBean
public interface BaseRepository<T,PK extends Serializable> extends JpaRepository<T,PK> {
    
}

6、分页查询和排序

package com.yuqiyu.chapter13.base;

import java.io.Serializable;
public class BaseEntity implements Serializable{

    /**

     * <p>

     * 分页页码,默认页码为1

     * <p>

     */
    protected int page = 1;
    /**

     * <p>

     * 分页每页数量,默认20条

     * <p>

     */
    protected int size = 20;
    /**

     * <p>

     * 排序列名称,默认为id

     * <p>

     */
    protected String sidx = "id";
    /**

     * <p>

     * 排序正序

     * <p>

     */
    protected String sord = "asc";
    public int getPage() {
        return page;
    }
    public void setPage(int page) {
        this.page = page;
    }
    public int getSize() {
        return size;
    }
    public void setSize(int size) {
        this.size = size;
    }
    public String getSidx() {
        return sidx;
    }
    public void setSidx(String sidx) {
        this.sidx = sidx;
    }
    public String getSord() {
        return sord;
    }
    public void setSord(String sord) {
        this.sord = sord;
    }
}
    /**
     * 分页查询测试
     * @param page 传入页码,从1开始
     * @return
     */
    @RequestMapping(value = "/cutpage")
    public List<UserEntity> cutPage(int page)
    {
        UserEntity user = new UserEntity();
        user.setSize(2);
        user.setSord("desc");
        user.setPage(page);
        //获取排序对象
        Sort.Direction sort_direction = Sort.Direction.ASC.toString().equalsIgnoreCase(user.getSord()) ? Sort.Direction.ASC : Sort.Direction.DESC;
        //设置排序对象参数
        Sort sort = new Sort(sort_direction, user.getSidx());
        //创建分页对象
        PageRequest pageRequest = new PageRequest(user.getPage() - 1,user.getSize(),sort);
        //执行分页查询
        return userJPA.findAll(pageRequest).getContent();
    }

7、SpringBoot validator

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

5、SpringBoot项目下SpringDataJPA与QueryDSL框架整合

参考:https://www.jianshu.com/p/7379173e1970

~~~~~~~~~~~~~~~~

7、Springboot启动配置原理

几个重要的事件回调机制

​ 配置在META-INF/spring.factories

ApplicationContextInitializer

SpringApplicationRunListener

只需要放在ioc容器中

ApplicationRunner

CommandLineRunner

启动流程:

1、创建SpringApplication对象

initialize(sources);
private void initialize(Object[] sources) {
    //保存主配置类
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    //判断当前是否一个web应用
    this.webEnvironment = deduceWebEnvironment();
    //从类路径下找到META-INF/spring.factories配置的所有ApplicationContextInitializer;然后保存起来
    setInitializers((Collection) getSpringFactoriesInstances(
        ApplicationContextInitializer.class));
    //从类路径下找到ETA-INF/spring.factories配置的所有ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    //从多个配置类中找到有main方法的主配置类
    this.mainApplicationClass = deduceMainApplicationClass();
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Iz19TzRb-1605780851807)(https://github.com/cyhbyw/springBoot_atguigu/raw/master/images/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20180306145727.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QcXddeq9-1605780851808)(https://github.com/cyhbyw/springBoot_atguigu/raw/master/images/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20180306145855.png)]

2、运行run方法

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   FailureAnalyzers analyzers = null;
   configureHeadlessProperty();
    
   //获取SpringApplicationRunListeners;从类路径下META-INF/spring.factories
   SpringApplicationRunListeners listeners = getRunListeners(args);
    //回调所有的获取SpringApplicationRunListener.starting()方法
   listeners.starting();
   try {
       //封装命令行参数
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
      //准备环境
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
       		//创建环境完成后回调SpringApplicationRunListener.environmentPrepared();表示环境准备完成
       
      Banner printedBanner = printBanner(environment);
       
       //创建ApplicationContext;决定创建web的ioc还是普通的ioc
      context = createApplicationContext();
       
      analyzers = new FailureAnalyzers(context);
       //准备上下文环境;将environment保存到ioc中;而且applyInitializers();
       //applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法
       //回调所有的SpringApplicationRunListener的contextPrepared();
       //
      prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
       //prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded();
       
       //s刷新容器;ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat);Spring注解版
       //扫描,创建,加载所有组件的地方;(配置类,组件,自动配置)
      refreshContext(context);
       //从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调
       //ApplicationRunner先回调,CommandLineRunner再回调
      afterRefresh(context, applicationArguments);
       //所有的SpringApplicationRunListener回调finished方法
      listeners.finished(context, null);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass)
               .logStarted(getApplicationLog(), stopWatch);
      }
       //整个SpringBoot应用启动完成以后返回启动的ioc容器;
      return context;
   }
   catch (Throwable ex) {
      handleRunFailure(context, listeners, analyzers, ex);
      throw new IllegalStateException(ex);
   }
}

3、事件监听机制

配置在META-INF/spring.factories

ApplicationContextInitializer

public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("ApplicationContextInitializer...initialize..."+applicationContext);
    }
}

SpringApplicationRunListener

public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {

    //必须有的构造器
    public HelloSpringApplicationRunListener(SpringApplication application, String[] args){

    }

    @Override
    public void starting() {
        System.out.println("SpringApplicationRunListener...starting...");
    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        Object o = environment.getSystemProperties().get("os.name");
        System.out.println("SpringApplicationRunListener...environmentPrepared.."+o);
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("SpringApplicationRunListener...contextPrepared...");
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("SpringApplicationRunListener...contextLoaded...");
    }

    @Override
    public void finished(ConfigurableApplicationContext context, Throwable exception) {
        System.out.println("SpringApplicationRunListener...finished...");
    }
}

配置(META-INF/spring.factories)

org.springframework.context.ApplicationContextInitializer=\
com.atguigu.springboot.listener.HelloApplicationContextInitializer

org.springframework.boot.SpringApplicationRunListener=\
com.atguigu.springboot.listener.HelloSpringApplicationRunListener

只需要放在ioc容器中

ApplicationRunner

@Component
public class HelloApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("ApplicationRunner...run....");
    }
}

CommandLineRunner

@Component
public class HelloCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("CommandLineRunner...run..."+ Arrays.asList(args));
    }
}

~~~~~~~~~~~~~~~~

8、SpringBoot自定义starters

starter:

1、这个场景需要使用到的依赖是什么?

2、如何编写自动配置
@Configuration //指定这个类是一个配置类
@ConditionalOnXXX //在指定的条件成立的情况下生效
@AutoConfigureAfter //指定自动配置类的顺序
@Bean //给容器中添加组件
@ConfigurationProperties //结合响应的xxxPropeties类来绑定相关的配置
@EnableConfigurationProperties //让xxxProperties生效加入到容器中

自动配置类能加载:需要将启动就加载的自动配置类,配置在META-INF/spring.factories

模式:

**别人只需要引入启动器(starter);而启动器依赖自动配置;**

启动器只用来做依赖导入;	

专门来写一个自动配置模块;

命名:

-mybatis-spring-boot-starter:自定义启动器命名 xxx-spring-boot-starter

自定义starter步骤如下:(demo参照百度云–>05.springboot自定义starter)

  • 自定义starter启动器(maven工程的方式),并在pom文件中引入自动配置工程的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.demo</groupId>
    <artifactId>hello-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
		<!--引入自动配置模块的maven坐标-->
        <dependency>
            <groupId>com.demo</groupId>
            <artifactId>hello-spring-boot-configure</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>
  • 自定义自动配置模块
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.demo</groupId>
    <artifactId>hello-spring-boot-configure</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>hello-spring-boot-configure</name>
    <description>Demo project for Spring Boot</description>

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

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies
        <!‐‐引入spring‐boot‐starter;所有starter的基本配置‐‐>          
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

    </dependencies>


</project>
  • 自定义自动配置类
@Configuration
//此应用为web应用时此类生效
@ConditionalOnWebApplication
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {

    @Autowired
    HelloProperties helloProperties;
    @Bean
    public HelloService helloService() {
        HelloService service = new HelloService();
        service.setHelloProperties(helloProperties);
        return service;
    }
}
  • 自定义自动配置properties
@ConfigurationProperties(prefix = "hello.say")
public class HelloProperties {
    private String prefix;

    private String suffix;

    private String age;

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

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}
  • 自定义初始化到容器中的实体
public class HelloService {

    HelloProperties helloProperties;

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

    public HelloProperties getHelloProperties() {
        return helloProperties;
    }

    public void setHelloProperties(HelloProperties helloProperties) {
        this.helloProperties = helloProperties;
    }
}
  • 在resources中创建META-INF/spring.factoris文件,并将自定义的自动配置类添加到springboot自动配置进行初始化自动装配
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.demo.HelloAutoConfiguration
  • 定义测试web工程(idea快速创建springboot项目是勾选web即可)
@RestController
public class HelloController {

    @Autowired
    HelloService helloService;
    @GetMapping("/hello")
    public String hello() {
        return helloService.sayHello("张三");
    }
}
================================
application.properties

hello.say.prefix=HELLO
hello.say.suffix=WORLD
hello.say.age=20

~~~~~~~~~~~~~~~~

9、SpringBoot与缓存

1、SpringBoot默认缓存机制

spring缓存抽象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nouuooAH-1605780851809)(./image/9.缓存概念.png)]

Cache、CacheManager

快速体验缓存

重点

1.开启基于注解的缓存 @EnableCaching

2.标注缓存注解即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E4c1qKB3-1605780851810)(./image/9.缓存注解.png)]

  • @Cacheable: 将方法的运行结果进行缓存,以后要相同的数据,直接从缓存中获取,不用调方法

CacheManager管理多个cache组件的,对缓存的真正crud操作在cache组件中,每个缓存组件都有自己唯一的一个名字

几个属性:

cacheNames/value:指定缓存组件的名字;
key:缓存数据使用的key:可以用它类指定。默认是使用方法参数的值 --> 1-方法的返回值
	编写spEL #id 参数ID的值 等价于 #a0,#p0,#root.args[0]
keyGenetator:key的生成器:可以自己指定key的生成器的组件id
	key、keyGenetator:二选一使用
cacheManager:指定缓存管理器,或者cacheResolver指定获取解析器
condition:指定符合条件的情况下才缓存;
unless:否定缓存,当unless指定的条件为true,方法的返回值就不会被缓存,可以获取到结果进行判断 unless="#result == null"
sync:是否使用异步模式  -->unless不能使用
  • @CachePut:既调用方法,也更新缓存数据
修改了数据库中的某个数据,同时更新缓存
运行时机:
	1. 先调用目标方法
	2. 将目标方法的结果缓存起来
  • @CacheEvict:缓存清除
key:指定要清楚的额数据
allEntries=true:指定清除这个缓存中所有的数据
beforeInvocation=false:缓存的清除是否在方法之前执行(默认是在方法执行之后执行,如果出现异常就不会清除)
beforeInvocation=true:在方法运行之前执行,无论方法是否出现异常,缓存都清除
  • @Caching 组合注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
    Cacheable[] cacheable() default {};

    CachePut[] put() default {};

    CacheEvict[] evict() default {};
}
  • @CacheConfig
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
    String[] cacheNames() default {};

    String keyGenerator() default "";

    String cacheManager() default "";

    String cacheResolver() default "";
}
  • 案例
@Service
@CacheConfig(cacheNames = "emp") //抽取公共属性
public class EmpService {

    @Autowired
    EmployeeMapper employeeMapper;
	//缓存key为id,lastName,email的属性 
    @Caching(
            cacheable = {
                    @Cacheable(key = "#lastName")
            }, put = {
            @CachePut(key = "#result.id"),
            @CachePut(key = "#result.email")
    }
    )
    public Employee findByLastName(String lastName) {
        return employeeMapper.findByLastName(lastName);
    }
}
  • 自定义KeyGenerator
@Configuration
public class CacheConfig {

    @Bean("myKeyGenerator")
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object o, Method method, Object... objects) {
                return method.getName()+"["+Arrays.asList(objects).toString()+"]";
            }
        };
    }
}

 //引用keyGenerator
	@Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator")
    public Employee findOne(Integer id) {
        System.out.println("查询员工信息");
        return employeeMapper.findById(id);
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jU1SAJoQ-1605780851811)(./image/9.cache–SpEL.png)]

启动时默认生效的缓存配置类:SimpleCacheConfiguration 给容器中注册了CacheManager组件:ConcurrentMapCacheManager

可以获取和创建ConcurrentMapCache类型的缓存组件,他的作用是将数据保存到ConcurrentMap中

运行流程:

使用@Cacheable执行流程:
	1.方法在运行之前,先查询Cache(缓存组件),按照cacheNames指定的名字获取;
		(CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建
	2.去Cache中查找缓存的内容,使用一个key,默认是方法的参数
		key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key
		SimpleKeyGenerator生成key的策略
			如果没有参数:	 key = new SimpleKey();
            如果有一个参数:key=参数的值
            如果有多个参数:key= newSimpleKey(params)
	3.没有查询到缓存就调用目标方法
	4.将目标方法返回的结果,放进缓存中
@Cacheable标注的方法执行之前先检查缓存中国有没有数据,默认按照参数的值作为key去查询缓存,如果没有就执行目标方法,将目标方法返回的结果放入缓存,以后再来调用就可以直接使用缓存中的数据;
核心:
	1.使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurentMapCache】组件
	2.key使用keyGenerator生成的,默认是SimpleKeyGenerator

2、SpringBoot整合redis

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

1、Redis安装(Docker镜像的方式 )

1.启动docker
[aaa@qq.com ~]# systemctl start docker
2.下载Redis镜像
[aaa@qq.com ~]# docker pull registry.docker-cn.com/library/redis
3.查看docker中的镜像
[aaa@qq.com ~]# docker images
REPOSITORY                             TAG                 IMAGE ID            CREATED             SIZE
registry.docker-cn.com/library/redis   latest              e1a73233e3be        45 hours ago        83.4 MB
4.启动Redis镜像,创建实例
[aaa@qq.com ~]# docker run -d -p 6379:6379 --name myredis registry.docker-cn.com/library/redis 
-d 表示后台运行 -p 表示端口映射(Redis端口:服务器或者虚拟机的端口)
5.查看启动的镜像实例 详情
[aaa@qq.com ~]# docker ps
CONTAINER ID        IMAGE                                  COMMAND                  CREATED             STATUS              PORTS                    NAMES
30072cf1139a        registry.docker-cn.com/library/redis   "docker-entrypoint..."   2 minutes ago       Up 2 minutes        0.0.0.0:6379->6379/tcp   myredis

2、Java操作Redis数据库

  • StringRedisTemplate
stringRedisTemplate.opsForValue(); [操作string字符串]
stringRedisTemplate.opsForList();  [操作list集合]
stringRedisTemplate.opsForHash();  [操作hash散列]
stringRedisTemplate.opsForSet();   [操作set集合]
stringRedisTemplate.opsForZSet()   [操作zset有序集合]
  • RedisTemplate
redisTemplate.opsForValue(); [操作string字符串]
redisTemplate.opsForList();  [操作list集合]
redisTemplate.opsForHash();  [操作hash散列]
redisTemplate.opsForSet();   [操作set集合]
redisTemplate.opsForZSet()   [操作zset有序集合]
  • 测试demo
package com.example.demo;

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

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

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Test
    public void contextLoads() {
        stringRedisTemplate.opsForValue().append("a", "hello");
        String a = stringRedisTemplate.opsForValue().get("a");
        System.out.println(a);
    }
}

3、自定义RedisTemplate通过序列化存储Json对象

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<Object, Employee> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Employee> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        template.setDefaultSerializer(new Jackson2JsonRedisSerializer<Employee>(Employee.class));
        return template;
    }
}
  • 引入Redis的starter,容器中保存的是RedisCacheManager
  • RedisCacheManager帮助我们创建RedisCache 来作为缓存组件,RedisCache操作Redis缓存
  • 默认报错数据K-V都市object:利用序列化保存
  • 如何保存json
    • 引入Redis的starter,cacheManager变为RedisCacheManager
    • 默认创建的RedisCacheManager操作Redis的时候使用的是RedisTemplate<Obeject,Object>
    • RedisTemplate<Obeject,Object>是默认使用JDK的序列化机制

4、CacheManager配置

  • SpringBoot1.x版本
    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        cacheManager.setUsePrefix(true);
        //设置过期时间
        cacheManager.setDefaultExpiration(3000);
        return cacheManager;
    }
  • 通过配置Spring的CacheManager为redis,即可指定使用redis做缓存,具体的配置方式跟1.0也有所不同,在1.0中使用RedisTemplate即可实例化一个RedisCacheManager:RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);,在2.0中删除了这个构造器,同时也不可以通过之前的setDefaultExpiration方法设置默认的缓存过期时间等,在新版本中可以通过以下的两种方式构造一个RedisCacheManager:

  • SpringBoot2.x版本

    • 通过RedisCacheManager的静态方法create:

      @Bean
      public CacheManager cacheManager(RedisConnectionFactory factory) {
          RedisCacheManager cacheManager = RedisCacheManager.create(factory);
          return cacheManager;
      }
      
    • 通过Spring提供的RedisCacheConfiguration类,构造一个自己的redis配置类,从该配置类中可以设置一些初始化的缓存命名空间、及对应的默认过期时间等属性,再利用RedisCacheManager中的builder.build()的方式生成cacheManager:

    @Bean
        public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
            //设置默认缓存的有效期以及key和value的序列化方式
            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                    .entryTtl(Duration.ofSeconds(60))
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class)))
                    .disableCachingNullValues();
    
            // 设置一个初始化的缓存空间set集合
            Set<String> cacheNames =  new HashSet<>();
            cacheNames.add("employee");
            cacheNames.add("depart");
    
            // 对每个缓存空间应用不同的配置
            Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
            configMap.put("employee", config);
            configMap.put("depart", config.entryTtl(Duration.ofSeconds(600)));
    
            RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory)
                    .cacheDefaults(config).initialCacheNames(cacheNames).withInitialCacheConfigurations(configMap)
                    .transactionAware()
                    .build();
    
            return redisCacheManager;
        }
    

~~~~~~~~~~~~~~~~

10、SpringBoot与消息(RabbitMQ)

1、概述

  • 点对点
    • 消息发送者发送消息,消息代理将其放入一个队列中,消息接收者从队列中获取消息内容,消息读取后被移除队列
    • 消息只有唯一的发送者和接受者,但不是说只能有一个接收者
  • 发布订阅式
    • 发送者(发布者)发送消息到主题,多个接收者(订阅者)监听(订阅)这个主题,那么就会在消息到达时同时收到消息
  • JMS(Java Message Service) Java消息服务:
    • 基于JVM消息代理的规范。ActiveMQ、HornetMQ是JMS实现的
  • AMQP(Advanced Message Queuing Protocol)
    • 高级消息队列协议,也是一个消息代理的规范,兼容JMS
    • RabbitMQ是AMQP的实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vy4c5WNv-1605780851812)(./image/10.JMS和AMQP对比.png)]

2、Spring支持

  • spring-jms提供了对JMS的支持
  • spring-rabbit提供了对AMQP的支持
  • 需要ConnectionFactory的实现来连接消息代理
  • 提供JmsTemplate、RabbitTemplate来发送消息
  • @JmsListener(JMS)、@RabbltListener(AMQP)注解在方法上监听消息代理发布消息
  • @EnableJms、@EnableRabbit开启支持

3、SpringBoot-MQ自动配置

  • JmsAutoConfiguration
  • RabbitAutoConfiguration

1、RabbitMQ简介

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YZpKVkoQ-1605780851813)(./image/10.rabbitMQ简介.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AzGbHPYn-1605780851814)(./image/10.rabbitMQ简介2.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YCK0pAEC-1605780851814)(./image/10.rabbitMQ简介3.png)]

交换器:决定消息发送到哪,根据路由键将交换器和消息队列进行绑定

2、RabbitMQ的Docker镜像安装

1.下载rabbitmq镜像
[aaa@qq.com ~]# docker pull registry.docker-cn.com/library/rabbitmq:3-management
2.查看镜像ID
[aaa@qq.com ~]# docker images
REPOSITORY                                TAG                 IMAGE ID            CREATED             SIZE
registry.docker-cn.com/library/redis      latest              e1a73233e3be        2 days ago          83.4 MB
registry.docker-cn.com/library/rabbitmq   latest              d502f687f36a        2 days ago          125 MB
3.根据镜像ID生成并启动实例
[aaa@qq.com ~]# docker run -d -p 5672:5672 -p 15672:15672 --name myrabbitmq d502f687f36a
4.查看运行状态
[aaa@qq.com ~]# docker ps
CONTAINER ID        IMAGE                                  COMMAND                  CREATED             STATUS              PORTS                                                                             NAMES
6ab564be385e        d502f687f36a                           "docker-entrypoint..."   7 minutes ago       Up 7 minutes        4369/tcp, 0.0.0.0:5672->5672/tcp, 5671/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp   myrabbitmq
30072cf1139a        registry.docker-cn.com/library/redis   "docker-entrypoint..."   15 hours ago        Up 15 hours         0.0.0.0:6379->6379/tcp                                                            myredis

注释 : d502f687f36a --> rabbitmq的镜像ID

3、RabbitMQ的界面化操作

参照: https://www.bilibili.com/video/av23478787?t=626&p=88

1)登录RabbitMQ

  • http://172.16.38.150:15672/

  • 用户名 guest

  • 密码guest

2)添加exchange交换器

  • 如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-idwpagQa-1605780851815)(./image/10.rabbitMQ的exchange添加.png)]

3) 添加消息队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wOQai4Ek-1605780851816)(./image/10.rabbitMQ添加消息queue.png)]

4)将交换器和 队列进行绑定

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vf30PHjV-1605780851817)(./image/10.exchange绑定topic类型.png)]

  • direct : 根据路由键匹配对应的队列
  • fanout:发送消息到所以的队列
  • topic: 根据定义的匹配规则,匹配相应的队列

4、SpringBoot集成RabbitMQ

  • RabbitMQ的几个核心

    • RabbitAutoConfiguration 自动配置类
    • ConnectionFactory自动配置了连接工厂
    • RabbitProperties封装了rabbitMQ的配置
    • RabbitTemplate给rabbitMQ发送和接收消息
    • AmqpAdmin rabbitMQ系统管理功能组件,创建和删除Queue,Exchange,Binding
    • @RabbitListener + @EnableRabbit 监听消息队列的内容
  • 引入maven坐标(或用idea创建时勾选rabbitMQ)

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  • 配置属性文件(application.yml)
spring:
  rabbitmq:
    addresses: 172.16.38.150
    username: guest
    password: guest
#    默认为 /
#    virtual-host: 
  • 编写测试文件
package com.example.demo;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

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

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Test
    public void contextLoads() {
        //对象被默认序列化后发送
        rabbitTemplate.convertAndSend("exchange.direct","hello","这是第一个消息");
    }

    @Test
    public void demo() {
        Object hello = rabbitTemplate.receiveAndConvert("hello");
        System.out.println(hello);
    }

}

  • 默认的序列化方式是以JDK序列化规则进行序列化(二进制数据存储)
  • 修改序列化方式为json,如下
package com.example.demo.config;

import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * ========================
 * Created with IntelliJ IDEA.
 * package: com.example.demo.config
 * author:kehong
 * className: RabbitConfig
 * projectName: springboot-rabbitmq
 * Date:2018-09-18
 * Time:下午3:46
 * description: ${DESCRIPTION}
 * ========================
 */
@Configuration
public class RabbitConfig {

    @Bean
    public MessageConverter myMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

}
  • Springboot中RabbitMQ生效配置
    • @RabbitListener + @EnableRabbit 监听消息
package com.example.demo.service;

import com.example.demo.bean.Book;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

/**
 * ========================
 * Created with IntelliJ IDEA.
 * package: com.example.demo.service
 * author:kehong
 * className: BookService
 * projectName: springboot-rabbitmq
 * Date:2018-09-18
 * Time:下午4:01
 * description: ${DESCRIPTION}
 * ========================
 */
@Service
public class BookService {
    //队列有内容,方法recieve会执行
    //反序列化数据到book对象中
    @RabbitListener(queues = "hello.news")
    public void recieve(Book book) {
        System.out.println(book);
    }
	
    @RabbitListener(queues = "hello.emps")
    public void recieve(Message message) {
        System.out.println(message.getBody());
        System.out.println(message.getMessageProperties());
    }
}
===========================================================
package com.example.demo;

import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableRabbit //开启基于注解的RabbitMQ
public class DemoApplication {

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

  • SpringBoot中系统管理功能操作
package com.example.demo;

import com.example.demo.bean.Book;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

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

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    AmqpAdmin amqpAdmin;

    @Test
    public void amqpAdmin() {
        //创建交换器
        amqpAdmin.declareExchange(new DirectExchange("amqpAdmin.exchange"));
        //创建队列
        amqpAdmin.declareQueue(new Queue("amqpAdmin.queue",true));
        //创建绑定规则
        amqpAdmin.declareBinding(new Binding("amqpAdmin.queue",Binding.DestinationType.QUEUE,"amqpAdmin.exchange","amqp.news",null));

}

~~~~~~~~~~~~~~~~

11、SpringBoot与搜索

1、Docker下安装elasticsearch

1.搜索镜像
[aaa@qq.com ~]# docker search elasticsearch

2.下载镜像
[aaa@qq.com ~]# docker pull registry.docker-cn.com/library/elasticsearch

3.查看镜像ID
[aaa@qq.com ~]# docker images
REPOSITORY                                     TAG                 IMAGE ID            CREATED             SIZE
registry.docker-cn.com/library/elasticsearch   latest              362c5cb1669b        2 days ago          486 MB
registry.docker-cn.com/library/redis           latest              e1a73233e3be        2 days ago          83.4 MB
registry.docker-cn.com/library/rabbitmq        3-management        2888deb59dfc        3 days ago          149 MB

4.根据镜像ID生成容器
[aaa@qq.com ~]# docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 -p 9300:9300 --name ES01 362c5cb1669b
3ed338952f83a9503bac88aa64c358415116f3d8f72b9c45c59d5e65c2032e6d
5. 查看运行状态
[aaa@qq.com ~]# docker ps
CONTAINER ID        IMAGE                                  COMMAND                  CREATED             STATUS              PORTS                                                                                        NAMES
71d635e2e017        362c5cb1669b                           "/docker-entrypoin..."   14 seconds ago      Up 13 seconds       0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp                                               ES01
eb38f708564e        2888deb59dfc                           "docker-entrypoint..."   7 hours ago         Up 7 hours          4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, 15671/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp   myrabbitmq
30072cf1139a        registry.docker-cn.com/library/redis   "docker-entrypoint..."   23 hours ago        Up 23 hours         0.0.0.0:6379->6379/tcp                                                                       myredis
  • 9200端口用于外部访问,9300用于内部节点之间的通讯

2、查看elasticsearch是否安装成功

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FCOhmRrq-1605780851817)(./image/11.elasticsearch访问.png)]

3、 elasticsearch概念

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5LMQufK3-1605780851818)(./image/11.elasticsearch概念图.png)]

4、elasticsearch的基本操作

1)通过restful风格往elasticsearch中存入数据

  • 每个雇员索引一个文档,包含该雇员的所有信息。
  • 每个文档都将是 employee 类型
  • 该类型位于 索引 megacorp 内。
  • 该索引保存在我们的 Elasticsearch 集群中。
PUT /megacorp/employee/1
{
    "first_name" : "John",
    "last_name" :  "Smith",
    "age" :        25,
    "about" :      "I love to go rock climbing",
    "interests": [ "sports", "music" ]
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G1aaegRl-1605780851819)(./image/11.elasticsearch:postman PUT操作.png)]

  • 注意,路径 /megacorp/employee/1 包含了三部分的信息:
 megacorp
    索引名称 
employee
    类型名称 
1
    特定雇员的ID 

2)通过restful风格检索文档

GET /megacorp/employee/1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CkA1XBoW-1605780851820)(./image/11.elasticsearch:postman GET操作.png)]

  • 将 HTTP 命令由 PUT 改为 GET 可以用来检索文档,同样的,可以使用 DELETE 命令来删除文档,以及使用 HEAD 指令来检查文档是否存在。如果想更新已存在的文档,只需再次 PUT
HEAD /megacorp/employee/1    //若检索存在返回Status:200 , 检索不存在则返回Status:404
DELETE /megacorp/employee/1

3)轻量搜索

  • 搜索所有雇员
GET /megacorp/employee/_search
  • 根据last_name=Smith检索
GET /megacorp/employee/_search?q=last_name:Smith

4)使用查询表达式搜索

POST /megacorp/employee/_search
{
    "query" : {
        "match" : {
            "last_name" : "Smith"
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2a8zslye-1605780851821)(./image/11.elasticsearch:postman POST操作.png)]

5)复杂搜索

  • 检索 last_name 和age大于30的员工
POST /megacorp/employee/_search
{
    "query" : {
        "bool": {
            "must": {
                "match" : {
                    "last_name" : "smith" 
                }
            },
            "filter": {
                "range" : {
                    "age" : { "gt" : 30 } 
                }
            }
        }
    }
}

6)全文检索

  • 全文检索会根据关键字进行分词搜索
POST /megacorp/employee/_search
{
    "query" : {
        "match" : {
            "about" : "rock climbing"
        }
    }
}

7)短语搜索

  • 根据关键字进行匹配搜索
POST /megacorp/employee/_search
{
    "query" : {
        "match_phrase" : {
            "about" : "rock climbing"
        }
    }
}

8)高亮搜索

POST /megacorp/employee/_search
{
    "query" : {
        "match_phrase" : {
            "about" : "rock climbing"
        }
    },
    "highlight": {
        "fields" : {
            "about" : {}
        }
    }
}

结果:

{
   ...
   "hits": {
      "total":      1,
      "max_score":  0.23013961,
      "hits": [
         {
            ...
            "_score":         0.23013961,
            "_source": {
               "first_name":  "John",
               "last_name":   "Smith",
               "age":         25,
               "about":       "I love to go rock climbing",
               "interests": [ "sports", "music" ]
            },
            "highlight": {
               "about": [
                  "I love to go <em>rock</em> <em>climbing</em>" 
               ]
            }
         }
      ]
   }
}
  • 更多搜索方式 请参照官方文

5、SpringBoot整合elasticsearch

1)springboot默认支持俩种技术和ES进行交互

  • Jest:默认不生效
    • 需导入Jest的工具包(io.searchbox.client.JestClient)
  • SpringData ElacticSearch
    • TransportClient:节点信息clusterNodes:clusterName

2)Jest方式整合es

  • 导入jest坐标依赖(注意版本问题)
<dependency>
    <groupId>io.searchbox</groupId>
    <artifactId>jest</artifactId>
    <version>5.3.4</version>
</dependency>
  • 设置application.yml
spring:
  elasticsearch:
    jest:
      uris: http://172.16.38.150:9200/
  • 使用JestClient
package com.example.demo.bean;

import io.searchbox.annotations.JestId;

/**
 * ========================
 * Created with IntelliJ IDEA.
 * package: com.example.demo.bean
 * author:kehong
 * className: Book
 * projectName: springboot-elasticsearch
 * Date:2018-09-25
 * Time:下午4:55
 * description: ${DESCRIPTION}
 * ========================
 */
public class Book {

    //标示主键
    @JestId
    private Integer id;

    private String name;

    private double price;

    private String author;

    public String getName() {
        return name;
    }

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

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }
}
package com.example.demo;

import com.example.demo.bean.Book;
import com.google.gson.Gson;
import io.searchbox.client.JestClient;
import io.searchbox.core.Index;
import io.searchbox.core.Search;
import io.searchbox.core.SearchResult;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;

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

    @Autowired
    private JestClient jestClient;
    @Test
    public void contextLoads() {
        //给es种索引(保存)一个文档
        Book book = new Book();
        book.setId(11);
        book.setName("平凡的世界");
        book.setPrice(20.0);
        book.setAuthor("路遥");

        //构建一个索引功能
        Index index = new Index.Builder(book).index("hello").type("book").build();

        try {
            //执行
            jestClient.execute(index);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //测试搜索
    @Test
    public void search(){
        String json = "{\n" +
                "    \"query\" : {\n" +
                "        \"match\" : {\n" +
                "            \"author\" : \"路遥\"\n" +
                "        }\n" +
                "    }\n" +
                "}";
        Search search = new Search.Builder(json).addIndex("hello").addType("book").build();
        try {
            SearchResult searchResult = jestClient.execute(search);
            System.out.println(searchResult.getJsonString());
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println(search);
    }


}

3)spring data elasticsearch整合es

  • spring data elasticsearch对应elasticsearch安装版本参考

    版本对应

  • 安装对应版本的elastic search

[aaa@qq.com ~]# docker pull registry.docker-cn.com/library/elasticsearch:5.5.0
[aaa@qq.com ~]# docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 -p 9300:9300 --name ES02 519c56205eb
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

6、 @Document

@Document注解里面的几个属性,类比mysql的话是这样:

index –> DB   
type –> Table   
Document –> row   

加上@Id注解后,在Elasticsearch里对应的该列就是主键了,在查询时就可以直接用主键查询。其实和mysql非常类似,基本就是一个数据库。

@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Document {
    String indexName();//索引库的名称,个人建议以项目的名称命名
    String type() default "";//类型,个人建议以实体的名称命名
    short shards() default 5;//默认分区数
    short replicas() default 1;//每个分区默认的备份数
    String refreshInterval() default "1s";//刷新间隔
    String indexStoreType() default "fs";//索引文件存储类型
}

7、 @Field

加上了@Document注解之后,默认情况下这个实体中所有的属性都会被建立索引、并且分词。
通过@Field注解来进行详细的指定,如果没有特殊需求,那么只需要添加@Document即可。

@Field注解的定义如下:  

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface Field {

    FieldType type() default FieldType.Auto;//自动检测属性的类型
    FieldIndex index() default FieldIndex.analyzed;//默认情况下分词
    DateFormat format() default DateFormat.none;
    String pattern() default "";
    boolean store() default false;//默认情况下不存储原文
    String searchAnalyzer() default "";//指定字段搜索时使用的分词器
    String indexAnalyzer() default "";//指定字段建立索引时指定的分词器
    String[] ignoreFields() default {};//如果某个字段需要被忽略
    boolean includeInParent() default false;
}

8、 ElasticsearchRepository

//不需要加@Component,直接可以@Autowared
package com.example.demo.repository;

import com.example.demo.bean.Book;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import java.util.List;
public interface BookRepository extends ElasticsearchRepository<Book,Integer> {
    /*
     * 功能描述:
     * @method: findByNameAndPrice
     * @param: [name, price]
     * @return: java.util.List<com.example.demo.bean.Book>
     * @date: 2018/9/30 下午6:23
     * @author: mac
     * 等价于
     * 发送一下json形式的查询表达式
        { "bool" :
            { "must" :
                [
                    { "field" : {"name" : "?"} },
                    { "field" : {"price" : "?"} }
                ]
            }
        }
     */
    //查询方式一
    public List<Book> findByNameAndPrice(String name, Integer price);
    //查询方式二
    @Query("{"bool" : {"must" : {"field" : {"name" : "?0"}}}}")
    Page<Book> findByName(String name, Pageable pageable);
    
}

package com.example.demo;

import com.example.demo.bean.Book;
import com.example.demo.repository.BookRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

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

    @Autowired
    BookRepository bookRepository;

    @Test
    public void contextLoads() {
        Book book = new Book();
        book.setId(1);
        book.setBookName("西游记");
        book.setPrice(35.0);
        bookRepository.index(book);
    }

}

异常:NoNodeAvailableException[None of the configured nodes are available: [{#transport#-1}{QD96g9OlRsGdO50_Yv6UqA}{172.16.38.150}{172.16.38.150:9200}]]

~~~~~~~~~~~~~~~~

12、SpringBoot异步任务

  • 开启异步任务注解

@EnableAsync //开启异步任务支持注解
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
  • 标示异步方法
package com.example.demo.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class HelloService {
    @Async //此注解表示在开启一条线程执行此方法,告诉Spring这是一个异步方法
    public void hello() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行hello方法");
    }
}
  • 测试

@RestController
public class HelloController {

    @Autowired
    private HelloService helloService;
	//无需等待service方法执行完
    @RequestMapping("/hello")
    public String hello() {
        helloService.hello();
        return "调用成功";
    }
}

~~~~~~~~~~~~~~~~

13、SpringBoot定时任务

  • 开启定时任务注解
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableScheduling //开启定时任务支持注解
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
  • 标示定时方法
package com.example.demo.service;

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

@Service
public class ScheduledService {

	/*
		一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素。按顺序依次为:


        秒(0~59)
        分钟(0~59)
        3 小时(0~23)
        4  天(0~31)
        5 月(0~11)
        6  星期(1~7 1=SUN 或 SUN,MON,TUE,WED,THU,FRI,SAT)
        年份(1970-2099)

		---------------------

         ,:枚举
         -:区间
         斜杠:步长
         ?:日/星期冲突匹配
         L:代表最后
         W:代表工作日
         C:和calendar联系后计算过的值
         #:星期,4#2,第二个星期四

	    每隔5秒执行一次:/5  * * * ?
        每隔1分钟执行一次:0 /1  * * ?
        0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
        0 0/30 9-17 * * ?   朝九晚五工作时间内每半小时
        0 0 12 ? * WED 表示每个星期三中午12点
        “0 0 12 * * ?” 每天中午12点触发 
        “0 15 10 ? * *” 每天上午10:15触发
        “0 15 10 * * ?” 每天上午10:15触发
        “0 15 10 * * ? *” 每天上午10:15触发
        “0 15 10 * * ? 2005” 2005年的每天上午10:15触发
        “0 * 14 * * ?” 在每天下午2点到下午2:59期间的每1分钟触发
        “0 0/5 14 * * ?” 在每天下午2点到下午2:55期间的每5分钟触发
        “0 0/5 14,18 * * ?” 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
        “0 0-5 14 * * ?” 在每天下午2点到下午2:05期间的每1分钟触发
        “0 10,44 14 ? 3 WED” 每年三月的星期三的下午2:10和2:44触发
        “0 15 10 ? * MON-FRI” 周一至周五的上午10:15触发
        “0 15 10 15 * ?” 每月15日上午10:15触发
        “0 15 10 L * ?” 每月最后一日的上午10:15触发
        “0 15 10 ? * 6L” 每月的最后一个星期五上午10:15触发
        “0 15 10 ? * 6L 2002-2005” 2002年至2005年的每月的最后一个星期五上午10:15触发
        “0 15 10 ? * 6#3” 每月的第三个星期五上午10:15触发
     */
    @Scheduled(cron = "0/5 * * * * *")
    public void scheduled() {
        System.out.println("定时任务执行了。。。");
    }
}

~~~~~~~~~~~~~~~~

14、SpringBoot邮件任务

  • 引入mail启动器
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
  • 配置application.yml
spring:
  mail:
    host: smtp.163.com
    username: aaa@qq.com
    password: ke0826
    properties:
      -mail.smtp.auth: true
      -mail.smtp.starttls.enable: true
      -mail.smtp.starttls.required: true
  • 测试邮件发送
package com.example.demo;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.test.context.junit4.SpringRunner;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;

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

    @Autowired
    JavaMailSenderImpl mailSender;

    @Test
    public void contextLoads() {
        //简单邮件发送
        /*SimpleMailMessage message = new SimpleMailMessage();
        message.setText("测试邮件");
        message.setTo("aaa@qq.com");
        message.setFrom("aaa@qq.com");*/

        //复杂邮件发送
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        try {
            //发送复杂的消息邮件 参数二:true表示是否上传附件
            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

            //邮件设置
            helper.setSubject("通知,今晚开会!");
            //true表示发送内容为html
            helper.setText("<b style='color:red'>今天7:30开会</b>",true);
            helper.setTo("aaa@qq.com");
            helper.setFrom("aaa@qq.com");

            //上传文件
            //helper.addAttachment("1.jpg",new File("d:\\1.jpg"));
            //helper.addAttachment("2.jpg",new File("d:\\2.jpg"));

            mailSender.send(mimeMessage);
        } catch (MessagingException e) {
            e.printStackTrace();
        }

    }

}

~~~~~~~~~~~~~~~~

15、SpringBoot与安全验证

1)、引入SpringSecurity:

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

2)、编写SpringSecurity的配置类:

@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    
}

3)、控制请求的访问权限:

package com.example.demo.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;

/**
 * ========================
 * Created with IntelliJ IDEA.
 * package: com.example.demo.config
 * author:kehong
 * className: MySecurityConfig
 * projectName: springboot-security
 * Date:2018-09-27
 * Time:上午11:39
 * description: ${DESCRIPTION}
 * ========================
 */
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //super.configure(http);
        //定制请求的授权规则
        //permitAll 表示所有人都可以访问 此处的/代表首页
        http.authorizeRequests().antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("VIP1")
                .antMatchers("/level2/**").hasRole("VIP2")
                .antMatchers("/level3/**").hasRole("VIP3");

        /*
         *开启自动配置的登录功能 效果:如果没有登录,没有权限就会来到登录页面
         *
         *功能描述:
         * 1、自动生成登录页面 /login
         * 2、登录失败:重定向到/login?error
         * 3、默认的post形式的/login代表处理登录,get形式的/login代表跳转到登录页面
         * 4、一旦自己定制loginPage,那么loginPage的post请求就是登录处理
         *    也可通过 loginProcessingUrl()指定登录处理路径
         * 5、usernameParameter 代表请求用户名参数名称设置 passwordParameter 代表请求密码参数设置
         *
         */
        http.formLogin().loginPage("/userlogin").usernameParameter("user").passwordParameter("pass");

        /*
         *开启自动配置的注销
         *
         *功能描述:
         * 1、访问 /logout 表示用户注销
         * 2、清空session
         * 3、logoutSuccessUrl 注销成功后 返回到哪里 此处的/代表首页
         */
        http.logout().logoutSuccessUrl("/");

        //开启记住我功能
        //登录成功后,将cookie发送给浏览器保存,以后登录带上这个cookie,只要通过安全检查就可以免登录
        //默认请求参数的名称为remember-me
        http.rememberMe().rememberMeParameter("remember");

    }
    //定义认证规则
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //super.configure(auth);
        //auth.jdbcAuthentication()  -->在数据库里查用户
        auth.inMemoryAuthentication()
                .withUser("zhangsan").password("123456").roles("VIP1", "VIP2")
                .and()
                .withUser("lisi").password("123456").roles("VIP1", "VIP3")
                .and()
                .withUser("wangwu").password("123456").roles("VIP2", "VIP3");

    }
}

4)、thymeleaf支持springsecurity

<properties>
    <thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
    <thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
    <thymeleaf-extras-springsecurity4.version>3.0.2.RELEASE</thymeleaf-extras-springsecurity4.version>
</properties>
 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 -->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity4</artifactId>
        </dependency>
    </dependencies>

5)、html中使用springsecurity

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
                xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div sec:authorize="!isAuthenticated()">
        游客您好,您尚未登录<a th:href="@{/login}">请登录</a>
    </div>
    <div sec:authorize="isAuthenticated()">
        <h2><span sec:authentication="name"></span>,您好,您的角色有:
            <span sec:authentication="principal.authorities"></span>
        </h2>
        <form th:action="@{/logout}" method="post">
            用户名:<input name="user"><br/>
            密码:<input name="pass"><br/>
            <input type="checkbox" name="remember"> 记住我<br/>
            <input type="submit" value="注销">
        </form>
    </div>

<div sec:authorize="hasRole('VIP1')">
    权限为VIP1 显示内容
</div>
<div sec:authorize="hasRole('VIP2')">
    权限为VIP2 显示内容
</div>
    <div sec:authorize="hasRole('VIP3')">
    权限为VIP3 显示内容
</div>
</body>
</html>

~~~~~~~~~~~~~~~~

16、分布式应用

1)dubbo+zookeeper的模式

  • docker下安装和启动zookeeper
[aaa@qq.com ~]# docker pull registry.docker-cn.com/library/zookeeper
[aaa@qq.com ~]# docker images
REPOSITORY                                     TAG                 IMAGE ID            CREATED                  SIZE
docker.io/zookeeper                            latest              89f7884dcc4e        Less than a second ago   148 MB
registry.docker-cn.com/library/elasticsearch   latest              362c5cb1669b        3 days ago               486 MB
registry.docker-cn.com/library/redis           latest              e1a73233e3be        3 days ago               83.4 MB
registry.docker-cn.com/library/rabbitmq        3-management        2888deb59dfc        3 days ago               149 MB
[aaa@qq.com ~]# docker run --name zk01 -p 2181:2181 --restart always -d 89f7884dcc4e
d06afa17e60a281f1d6d938bf20ecd0493fc70d7bdc383640c469f88fef73024
[aaa@qq.com ~]# docker ps 
CONTAINER ID        IMAGE                                  COMMAND                  CREATED             STATUS              PORTS                                                                                        NAMES
d06afa17e60a        89f7884dcc4e                           "/docker-entrypoin..."   3 minutes ago       Up 3 minutes        2888/tcp, 0.0.0.0:2181->2181/tcp, 3888/tcp                                                   zk01
71d635e2e017        362c5cb1669b                           "/docker-entrypoin..."   12 hours ago        Up 12 hours         0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp                                               ES01
eb38f708564e        2888deb59dfc                           "docker-entrypoint..."   20 hours ago        Up 20 hours         4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, 15671/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp   myrabbitmq
30072cf1139a        registry.docker-cn.com/library/redis   "docker-entrypoint..."   36 hours ago        Up 36 hours         0.0.0.0:6379->6379/tcp     
<dependency>
    <groupId>com.alibaba.boot</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>0.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.101tec/zkclient -->
<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.10</version>
</dependency>
server:
  port: 8081
dubbo:
  application:
    name: customer-dubbo
  registry:
    address: zookeeper://172.16.38.150:2181
  scan:
    base-packages: com.example.demo.service
package com.example.demo.service;


public interface ProviderService {

    public void dubbo();
}
package com.example.demo.service.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.example.demo.service.ProviderService;
import org.springframework.stereotype.Component;

@Service 
@Component
public class ProviderServiceImpl implements ProviderService {

    public void dubbo() {
        System.out.println("dubbo-service执行了");
    }
}
  • customer配置
<dependency>
    <groupId>com.alibaba.boot</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>0.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.101tec/zkclient -->
<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.10</version>
</dependency>
<!--引入provider 打包到本地仓库的坐标依赖-->
<dependency>
    <groupId>com.example</groupId>
    <artifactId>provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
dubbo:
  application:
    name: customer-dubbo
  registry:
    address: zookeeper://172.16.38.150:2181
server:
  port: 8080
package com.example.demo.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.example.demo.service.ProviderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @Autowired
    //@Reference
    ProviderService providerService;

    @RequestMapping(value = "dubbo")
    public void dubbo() {
        providerService.dubbo();
    }
}

2)SpringBoot+SpringCloud的模式

1、SpringCloud概述

  • Spring Cloud是一个分布式的整体解决方案,Spring Cloud为开发者提供了在分布式系统(配置管理,服务发现,熔断,路由,微代理,控制总线,一次性token,全局锁,leader选举,分布式session,集群状态)中快速构建的工具,使用Spring Cloud的开发者可以快速的启动服务或构建应用、同时能够快速和云平台资源进行对接。
  • Spring Cloud分布式开发的五大常用组件
    • 服务发现—Netflix Eureka 等价于zookeeper
    • 客户端负载均衡—Netflix Ribbon
    • 断路器—Netflix Hystrix
    • 服务网关—Netflix Zuul
    • 分布式配置—Spring Cloud Confi

2、SpringCloud注册中心Eureka

  • 创建配置注册中心Eureka

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x4D0WrP8-1605780851822)(./image/16.springcloud eureka创建.png)]

1、 配置eureka相关信息

server:
  port: 8761
eureka:
  instance:
    hostname: eureka-server #eureka实例的主机名

  client:
    #不将自己注册到eureka上
    register-with-eureka: false
    #不从eureka上获取服务的注册信息
    fetch-registry: false
    service-url:
      #默认值为 http://localhost:8761/eureka/
      defaultZone: http://localhost:8761/eureka/	

2、开启@EnableEurekaServer注解

package com.example.eurekaserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

//注册中心
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

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

3、启动并访问http://localhost:8761/观察eureka是否配置正确

  • 创建生产者或消费者

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DaTnjgi1-1605780851823)(./image/16.springclod 生产者或消费者创建.png)]

  • 生产者配置
package com.example.providerticket.service;

import org.springframework.stereotype.Service;

@Service
public class TicketService {

    public String getTicket() {
        return "《厉害了,我的国》";
    }
}
==================================================================
package com.example.providerticket.controller;

import com.example.providerticket.service.TicketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TicketController {
    @Autowired
    TicketService ticketService;

    @GetMapping("ticket")
    public String getTicket() {
        return ticketService.getTicket();
    }
}

server:
  port: 8001
spring:
  application:
    name: provider-ticket

eureka:
  instance:
    #注册服务时,使用服务的IP地址
    prefer-ip-address: true
  client:
    service-url:
      #默认值为 http://localhost:8761/eureka/
      defaultZone: http://localhost:8761/eureka/
  • 消费者配置
package com.example.customeruser.controller;

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

@RestController
public class UserController {

    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/buy")
    public String buyTicket(String name) {
        String s = restTemplate.getForObject("http://PROVIDER-TICKET/ticket", String.class);
        return name + "购买了:" + s;
    }
}
==============================================================================================
    
package com.example.customeruser;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableDiscoveryClient //开启发现服务功能
public class CustomerUserApplication {

    public static void main(String[] args) {
        SpringApplication.run(CustomerUserApplication.class, args);
    }
    @Bean
    @LoadBalanced //使用负载均衡机制
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
server:
  port: 8002
spring:
  application:
    name: customer-user

eureka:
  instance:
    #注册服务时,使用服务的IP地址
    prefer-ip-address: true
  client:
    service-url:
      #默认值为 http://localhost:8761/eureka/
      defaultZone: http://localhost:8761/eureka/
  • 异常
 Invocation of destroy method failed on bean with name 'scopedTarget.eurekaClient': org.springframework.beans.factory.BeanCreationNotAllowedException: Error creating bean with name 'eurekaInstanceConfigBean': Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)

解决办法 : 开始在创建工程时未引入stater-web启动器

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

~~~~~~~~~~~~~~~~

17、SpringBoot开发热部署

  • 引入maven坐标
    • idea下 ctrl+f9
    • eclipse下 ctrl+s
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

~~~~~~~~~~~~~~~~

18、SpringBoot与监控管理

  • 通过引入spring-boot-starter-actuator,可以通过Spring Boot为我们提供准生产环境下应用监控和管理功能,我们可以通过HTTP JMX SSH协议来进行操作,自动得到审计、健康及指标信息等。
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nvaKITBd-1605780851825)(./image/18.actuator引入.png)]

  • SpringBoot1.x版本
#server.port=8081
#1、management.security.enabled默认为true,无访问权限的
#2、默认启用所有的监控端点
management.security.enabled=false
#开启远程关闭 post请求
# endpoints.shutdown.enabled=true
#关闭所有的端点
#endpoints.enabled=false
#开启beans端点
#endpoints.beans.enabled=true
  • SpringBoot2.x版本
#1、默认端点 path 前面多了一级 /actuator 。
#2、同时注意只有端点/health和/info端点是暴露的。
#3、通过如下配置开启所有监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"
    #关闭所有端点
    #enabled-by-default: false

  server:
  	#管理端点端口号
    port: 10111
    servlet:
      context-path: /
    ssl:
      enabled: false
  endpoint:
  	#开启远程关闭,post请求
    #shutdown:
    #    enabled: true
    health:
      show-details: always
    #开启beans端点
    #beans:
    #  enabled: true  
  • management.endpoint.health.show-details的值除了always之外还有when-authorizednever,默认值是never
  • SpringBoot1.x版本和SpringBoot2.x版本actuator不同参照
    • https://blog.csdn.net/qq_35915384/article/details/80203768
  • 监控和管理端点
端点名 描述
autoconfig 所有自动配置信息
auditevents 审计事件
beans 所有的bean信息
configprops 所有配置信息
dump 线程状态信息
env 当前环境信息
health 应用状态信息(含引入的starter)
info 当前应用信息
metrics 应用的各项指标
mappings 应用@RequestMapping映射路径
shutdown 关闭当前应用(默认关闭)
trace 追踪信息(最新的http请求)

1、健康检查的原理

Spring boot的健康信息都是从ApplicationContext中的各种HealthIndicator
Beans中收集到的,Spring boot框架中包含了大量的HealthIndicators的实现类,当然你也可以实现自己认为的健康状态。

默认情况下,最终的spring boot应用的状态是由HealthAggregator汇总而成的,汇总的算法是:

  1. 设置状态码顺序:setStatusOrder(Status.DOWN, Status.OUT_OF_SERVICE, Status.UP, Status.UNKNOWN);
  2. 过滤掉不能识别的状态码。
  3. 如果无任何状态码,整个spring boot应用的状态是 UNKNOWN
  4. 将所有收集到的状态码按照 1 中的顺序排序。
  5. 返回有序状态码序列中的第一个状态码,作为整个spring boot应用的状态。
  • Spring boot框架自带的 HealthIndicators 目前包括在此路径下:
    • spring-boot-actuator/2.0.5.RELEASE/spring-boot-actuator-2.0.5.RELEASE.jar!/org/springframework/boot/actuate/health

你可以通过management.health.defaults.enabled这个配置项将它们全部禁用掉,也可以通过management.health.xxxx.enabled将其中任意一个禁用掉。

2、自定义 HealthIndicator 健康检查

import org.springframework.boot.actuate.health.Health; 
import org.springframework.boot.actuate.health.HealthIndicator; 
import org.springframework.stereotype.Component; 
@Component public class MyHealthIndicator implements HealthIndicator { 
    @Override public Health health() { 
        int errorCode = check(); // perform some specific health check 
        if (errorCode != 0) { 
            return Health.down().withDetail("Error Code", errorCode).build(); 
        } 
        return Health.up().build(); 
    }
}

另外,除了Spring boot定义的几个状态类型,我们也可以自定义状态类型,用来表示一个新的系统状态。在这种情况下,你还需要实现接口 HealthAggregator ,或者通过配置 management.health.status.order 来继续使用HealthAggregator的默认实现。

例如,在你自定义的健康检查HealthIndicator的实现类中,使用了自定义的状态类型FATAL,为了配置该状态类型的严重程度,你需要在application的配置文件中添加如下配置:

management.health.status.order=FATAL, DOWN, OUT_OF_SERVICE, UNKNOWN, UP

在做健康检查时,响应中的HTTP状态码反应了整体的健康状态,(例如,UP 对应 200, 而 OUT_OF_SERVICEDOWN 对应 503)。同样,你也需要为自定义的状态类型设置对应的HTTP状态码,例如,下面的配置可以将 FATAL 映射为 503(服务不可用):

management.health.status.http-mapping.FATAL=503

如果你需要更多的控制,你可以定义自己的 HealthStatusHttpMapper bean。

下面是内置健康状态类型对应的HTTP状态码列表:

Status Mapping
DOWN SERVICE_UNAVAILABLE (503)
OUT_OF_SERVICE SERVICE_UNAVAILABLE (503)
UP No mapping by default, so http status is 200
UNKNOWN No mapping by default, so http status is 200

~~~~~~~~~~~~~~~~

19、SpringBoot使用 Lombok

1、引入 Lombok

  • maven坐标的方式
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
  • 快速创建的形式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KhPzQKIV-1605780851826)(./image/19.添加Lombok依赖.png)]

  • 添加idea支持

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-unQywLDa-1605780851827)(./image/19.idea安装Lombok支持插件.png)]

2、 Lombok有哪些注解

  • @Setter
  • @Getter
  • @Data
  • @Log(这是一个泛型注解,具体有很多种形式)
  • @AllArgsConstructor
  • @NoArgsConstructor
  • @EqualsAndHashCode
  • @NonNull
  • @Cleanup
  • @ToString
  • @RequiredArgsConstructor
  • @Value
  • @SneakyThrows
  • @Synchronized

3、 部分注解演示

package com.example.demo.entity;

import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

/**
 * ========================
 * 
 */
//@Getter
//@Setter 			在使用注解时,会默认生成一个无参构造。和对应的getter、setter方法 
//@ToString 
@Data 				//该注解使用在类上,该注解会提供getter、setter、equals、canEqual、hashCode、toString方法。
@AllArgsConstructor //该注解使用在类上,该注解提供一个全参数的构造方法,默认不提供无参构造。 
@NoArgsConstructor  //该注解使用在类上,该注解提供一个无参构造 
//@Value			这个注解用在 类 上,会生成含所有参数的构造方法,get 方法,此外还提供了equals、hashCode、toString 方法。注意:没有setter
public class UserEntity {
    private String name;

    private String sex;

    private String height;
}
package com.example.demo;

import com.example.demo.entity.UserEntity;
import lombok.extern.java.Log;
import lombok.extern.log4j.Log4j;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
//@Slf4j
@Log
public class DemoApplicationTests {

    @Test
    public void contextLoads() {
        UserEntity entity = new UserEntity();
        entity.setName("张三");
        entity.setHeight("175cm");
        entity.setSex("男");
        System.out.println(log.getClass());
        log.info(entity.toString());
        //System.out.println(entity);
    }

}
-------------------------------------------------------------
class java.util.logging.Logger
2018-10-08 16:53:33.559  INFO 1054 --- [           main] com.example.demo.DemoApplicationTests    : UserEntity(name=张三, sex=, height=175cm)

4、关于log注解

注解在 上。有如下可选择可用:

//@CommonsLog
private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
//@JBossLog
private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class);
//@Log
private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
//@Log4j
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);
//@Log4j2
private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
//@Slf4j
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
//@XSlf4j
private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);

5、更多操作