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

Java相关 专栏收录该内容
7 篇文章 0 订阅

#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)配置文件

10.@Configuration注解类上的@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 Log4jJUL(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"); 
    }
}

图示:

img

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

2、遗留问题

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

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

img

解决办法: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.fileLogging.pathexampleDescription
      (none)(none)只在控制台输出
      指定文件(none)My.log输出日志到my。log
      (none)指定目录/var/log输出到指定目录的spring。log文件中
      1. 指定配置

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

        logging system
        logbacklogback-spring.groovy , logback.xml or logback.groovy
        Log4j2log4j2-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 {
    //方式一
    /*ViewControllerRegistry@Override
    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
查询getEmpemp—GET
添加addEmp?xxxemp—POST
修改updateEmp?id=xxx&xxx=xxemp/{id}—PUT
删除deleteEmp?id=1emp/{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="delUrl=@{/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.显示容器内文件和文件夹
root@73f01be51ee8:/usr/share/elasticsearch# ls -l
14.进入某一个文件夹
root@73f01be51ee8:/usr/share/elasticsearch# cd config/
15.编辑文件,由于docker下未安装vim/vi指令
root@73f01be51ee8:/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
[root@localhost ~]# docker run ‐‐name mysql01 ‐e MYSQL_ROOT_PASSWORD=123456 ‐d mysql
3.查看错误日志
[root@localhost ~]# 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
[root@172 ~]# systemctl start docker
2.下载Redis镜像
[root@172 ~]# docker pull registry.docker-cn.com/library/redis
3.查看docker中的镜像
[root@172 ~]# docker images
REPOSITORY                             TAG                 IMAGE ID            CREATED             SIZE
registry.docker-cn.com/library/redis   latest              e1a73233e3be        45 hours ago        83.4 MB
4.启动Redis镜像,创建实例
[root@172 ~]# docker run -d -p 6379:6379 --name myredis registry.docker-cn.com/library/redis 
-d 表示后台运行 -p 表示端口映射(Redis端口:服务器或者虚拟机的端口)
5.查看启动的镜像实例 详情
[root@172 ~]# 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镜像
[root@localhost ~]# docker pull registry.docker-cn.com/library/rabbitmq:3-management
2.查看镜像ID
[root@localhost ~]# 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生成并启动实例
[root@localhost ~]# docker run -d -p 5672:5672 -p 15672:15672 --name myrabbitmq d502f687f36a
4.查看运行状态
[root@localhost ~]# 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.搜索镜像
[root@localhost ~]# docker search elasticsearch

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

3.查看镜像ID
[root@localhost ~]# 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生成容器
[root@localhost ~]# docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 -p 9300:9300 --name ES01 362c5cb1669b
3ed338952f83a9503bac88aa64c358415116f3d8f72b9c45c59d5e65c2032e6d
5. 查看运行状态
[root@localhost ~]# 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

[root@172 ~]# docker pull registry.docker-cn.com/library/elasticsearch:5.5.0
[root@172 ~]# 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: kehongts@163.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("1170586300@qq.com");
        message.setFrom("kehongts@163.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("1170586300@qq.com");
            helper.setFrom("kehongts@163.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
[root@172 ~]# docker pull registry.docker-cn.com/library/zookeeper
[root@172 ~]# 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
[root@172 ~]# docker run --name zk01 -p 2181:2181 --restart always -d 89f7884dcc4e
d06afa17e60a281f1d6d938bf20ecd0493fc70d7bdc383640c469f88fef73024
[root@172 ~]# 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状态码列表:

StatusMapping
DOWNSERVICE_UNAVAILABLE (503)
OUT_OF_SERVICESERVICE_UNAVAILABLE (503)
UPNo mapping by default, so http status is 200
UNKNOWNNo 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、更多操作

  • 0
    点赞
  • 1
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

评论 1 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

码农小站

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值