一.Spring Boot 入门 1.SpringBoot简介简化Spring应用开发的一个框架 整个Spring技术栈的一个大整合 J2EE开发的一站式解决方案
优点快速创建独立运行的Spring项目以及与主流框架集成 使用嵌入式的Servlet容器,应用无需打成WAR包 starters自动依赖与版本控制 大量的自动配置,简化开发,也可修改默认值 无需配置XML,无代码生成,开箱即用 准生产环境的运行时应用监控 与云计算的天然集成 2.微服务2014,martin fowler
微服务:架构风格(服务微化)
一个应用应该是一组小型服务;可以通过HTTP的方式进行互通;
单体应用:ALL IN ONE
微服务:每一个功能元素最终都是一个可独立替换和独立升级的软件单元;
详细参照微服务文档
集群、分布式、微服务概念和区别
3.环境约束–jdk1.8:Spring Boot 推荐jdk1.7及以上;
–maven3.x:maven 3.3以上版本;
4.Maven设置给maven 的settings.xml配置文件的profiles标签添加:(设置使用的jdk版本)
开发工具中的maven设置为自己配置的maven
1 2 3 4 5 6 7 8 9 10 11 12 <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 >
创建一个maven工程 导入spring boot相关的依赖1 2 3 4 5 6 7 8 9 10 11 12 13 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.2.1.RELEASE</version > <relativePath /> </parent > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > </dependencies >
编写一个主程序;启动Spring Boot应用1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package cn.clboy.springboot;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication public class HelloWorldMainApplication { public static void main (String[] args) { SpringApplication.run(HelloWorldMainApplication.class, args); } }
编写一个Controller1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package cn.clboy.springboot.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@Controller public class HelloController { @ResponseBody @RequestMapping("hello") public String hello () { return "hello world" ; } }
行主程序Main方法测试访问 http://localhost:8080/hello
简化部署添加maven插件
1 2 3 4 5 6 7 8 9 <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build >
使用mvn package进行打包
进入打包好的jar包所在目录
使用 java -jar jar包名称
运行
5.Hello World探究 依赖1 2 3 4 5 6 7 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.2.1.RELEASE</version > <relativePath /> </parent >
1 2 3 4 5 6 7 8 9 10 11 12 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-dependencies</artifactId > <version > 2.2.1.RELEASE</version > <relativePath > ../../spring-boot-dependencies</relativePath > </parent >
启动器1 2 3 4 <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相关场景的所有依赖都会导入进来。要用什么功能就导入什么场景的启动器
主程序类,主入口类1 2 3 4 5 6 7 8 @SpringBootApplication public class HelloWorldMainApplication { public static void main (String[] args) { SpringApplication.run(HelloWorldMainApplication.class, args); } }
@SpringBootApplication
: Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;@SpringBootApplication注解等于@EnableAutoConfiguration注解加上@ComponentScan注解。
看一下@SpringBootApplication这个注解类的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @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的配置类;
1 2 3 4 5 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration
@Configuration
:配置类上来标注这个注解;
配置类 ----- 配置文件;配置类也是容器中的一个组件;@Component
1 2 3 4 5 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration
@EnableAutoConfiguration
:开启自动配置功能;
以前我们需要配置的东西,SpringBoot帮我们自动配置;@EnableAutoConfiguration 告诉SpringBoot开启自动配置功能;这样自动配置才能生效;
1 2 3 4 5 6 7 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration
@AutoConfigurationPackage
:自动配置包
1 2 3 4 5 6 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import({Registrar.class}) public @interface AutoConfigurationPackage
@Import
:Spring的底层注解@Import,给容器中导入一个类。有时没有把某个类注入到IOC容器中,但在运用的时候需要获取该类对应的bean,此时就需要用到@Import注解。可以直接拿到该类的对象。
导入的组件由org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar 将主配置类(@SpringBootApplication标注的类 )的所在包及下面所有子包里面的所有组件扫描到Spring容器;
这里controller包是在主程序所在的包下,所以会被扫描到,我们在springboot包下创建一个test包,把主程序放在test包下,这样启动就只会去扫描test包下的内容而controller包就不会被扫描到,再访问开始的hello就是404
@Import({AutoConfigurationImportSelector.class})
AutoConfigurationImportSelector.class 将所有需要导入的组件以全类名的方式返回;这些组件就会被添加到容器中;会给容器中导入非常多的自动配置类(xxxAutoConfiguration);就是给容器中导入这个场景需要的所有组件,并配置好这些组件;
有了自动配置类,免去了我们手动编写配置注入功能组件等的工作;
可见selectImports()是AutoConfigurationImportSelector的核心函数,其核心功能就是获取spring.factories中EnableAutoConfiguration所对应的Configuration类列表,由@EnableAutoConfiguration注解中的exclude/excludeName参数筛选一遍,再由AutoConfigurationImportFilter类所有实例筛选一遍,得到最终的用于Import的configuration和exclusion。
Spring Boot在启动的时候从类路径下的META-INF/spring.factories
中获取EnableAutoConfigura tion指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作;以前我们需要自己配置的东西,自动配置类都帮我们完成了;
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public class AutoConfigurationImportSelector implements DeferredImportSelector , BeanClassLoaderAware , ResourceLoaderAware , BeanFactoryAware , EnvironmentAware , Ordered { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this .beanClassLoader); AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } protected boolean isEnabled (AnnotationMetadata metadata) { if (getClass() == AutoConfigurationImportSelector.class) { return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true ); } return true ; } protected AutoConfigurationEntry getAutoConfigurationEntry (AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); } }```
二、配置文件 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:配置例子
XML:
1 2 3 <server > <port > 8081</port > </server >
2、YAML语法: 1、基本语法k:(空格)v:表示一对键值对(空格必须有);
以空格 的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级的
1 2 3 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的方式
1 2 3 friends: lastName: zhangsan age: 20
行内写法:
1 friends: {lastName: zhangsan ,age: 18 }
数组(List、Set):用- 值表示数组中的一个元素
1 2 3 4 pets: - cat - dog - pig
行内写法
3、配置文件值注入配置文件
1 2 3 4 5 6 7 8 9 10 11 12 person: lastName: hello age: 18 boss: false birth: 2017 /12/12 maps: {k1: v1 ,k2: 12 } lists: - lisi - zhaoliu dog: name: 小狗 age: 12
javaBean:
@ConfiguratoionProperties(prefix = "配置文件的前缀")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @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;
我们可以导入配置文件处理器,以后编写配置就有提示了
1 2 3 4 5 6 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-configuration-processor</artifactId > <optional > true</optional > </dependency >
1、properties配置文件在idea中默认utf-8可能会乱码1 2 3 4 5 6 7 8 9 10 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.last-name:hello}_dog person.dog.age =15
调整
在设置中找到File Encodings,将配置文件字符集改为UTF-8,并勾选:
[x] Transparent native-to-ascii conversion
2、@Value获取值和@ConfigurationProperties获取值比较1 2 3 4 5 6 @Value("${person.last-name}") private String lastName; @Value("#{11*2}") private Integer age; @Value("true") private Boolean boss;
@ConfigurationProperties @Value 功能 批量注入配置文件中的属性 一个个指定 松散绑定(松散语法) 支持 不支持 SpEL 不支持 支持 JSR303数据校验 支持 不支持 复杂类型封装 支持 不支持
配置文件yml还是properties他们都能获取到值;
如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value;
如果说,我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties;
注意 :
@ConfigurationProperties里面的数据可以直接注入属性,并且优先级最高 @PropertySource里面的数据需要通过@Value(${}) 3、配置文件注入值数据校验1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Component @ConfigurationProperties(prefix = "person") @Validated public class Person { @Email private String lastName; private Integer age; private Boolean boss; private Date birth; private Map<String,Object> maps; private List<Object> lists; private Dog dog;
4、@PropertySource&@ImportResource&@Bean@PropertySource
:加载指定的配置文件;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @PropertySource(value = {"classpath:person.properties"}) @Component @ConfigurationProperties(prefix = "person") public class Person { private String lastName; private Integer age; private Boolean boss;
@ImportResource
:导入Spring的配置文件,让配置文件里面的内容生效;
Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别;
想让Spring的配置文件生效,加载进来;@ImportResource 标注在一个配置类上
1 2 @ImportResource(locations = {"classpath:beans.xml"}) 导入Spring的配置文件让其生效
1 2 3 4 5 6 7 8 <?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 >
不来编写Spring的配置文件
SpringBoot推荐给容器中添加组件的方式;推荐使用全注解的方式
1、配置类 @Configuration
------>Spring配置文件
2、使用 @Bean
给容器中添加组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Configuration public class MyAppConfig { @Bean public HelloService helloService02 () { System.out.println("配置类@Bean给容器中添加组件了..." ); return new HelloService(); } }
4、配置文件占位符 1、随机数1 2 3 ${random.value}、${random.int }、${random.long } ${random.int (10 )}、${random.int [1024 ,65536 ]}
2、占位符获取之前配置的值,如果没有可以是用:指定默认值1 2 3 4 5 6 7 8 9 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.last-name:hello}_dog person.dog.age =15
5、Profile 1、多Profile文件我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml
默认使用application.properties的配置,可以在application.properties中写spring.profiles.active=dev
来指定使用哪一个配置文件 ;
2、yml支持多文档块方式1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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的默认配置文件(并且properties比yml的优先级高 ,会限制性yml,后面的properties进行覆盖)
–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.命令行参数
所有的配置都可以在命令行上进行指定
1 java -jar spring-boot-02 -config-02 -0.0 .1 -SNAPSHOT.jar --server.port=8087 --server.servlet.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指定的默认属性
所有支持的配置加载来源;
参考官方文档
8、自动配置原理配置文件到底能写什么?怎么写?自动配置原理;
配置文件能配置的属性参照
1、自动配置原理: 1)、SpringBoot启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfiguration
2)、@EnableAutoConfiguration 作用:
利用EnableAutoConfigurationImportSelector给容器中导入一些组件?
可以查看selectImports()方法的内容;
Listconfigurations = getCandidateConfigurations(annotationMetadata, attributes);获取候选的配置
SpringFactoriesLoader.loadFactoryNames() 扫描所有jar包类路径下 META-INF/spring.factories 把扫描到的这些文件的内容包装成properties对象 从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加在容器中
将 类路径下 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中;
所有需要导入的AutoConfiguration
#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.CassandraRepositoriesAutoConfiguration,
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,
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.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.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.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.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.flyway.FlywayAutoConfiguration,
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,
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.mobile.DeviceResolverAutoConfiguration,
org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,
org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,
org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,
org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,
org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,
org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,
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.DispatcherServletAutoConfiguration,
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,
org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,
org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration
每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置;
3)、每一个自动配置类进行自动配置功能;
4)、以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例解释自动配置原理;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package org.springframework.boot.autoconfigure.web.servlet;...... @Configuration( proxyBeanMethods = false ) @EnableConfigurationProperties({HttpProperties.class}) @ConditionalOnWebApplication( type = Type.SERVLET ) @ConditionalOnClass({CharacterEncodingFilter.class}) @ConditionalOnProperty( prefix = "spring.http.encoding", value = {"enabled"}, matchIfMissing = true ) public class HttpEncodingAutoConfiguration { private final Encoding properties; public HttpEncodingAutoConfiguration (HttpProperties properties) { this .properties = properties.getEncoding(); } @Bean @ConditionalOnMissingBean public CharacterEncodingFilter characterEncodingFilter () { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this .properties.getCharset().name()); filter.setForceRequestEncoding(this .properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST)); filter.setForceResponseEncoding(this .properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE)); return filter; } ......
根据当前不同的条件判断,决定这个配置类是否生效?
一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
5)、所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;配置文件能配置什么就可以参照某个功能对应的这个属性类
1 2 3 4 @ConfigurationProperties(prefix = "spring.http.encoding") public class HttpEncodingProperties { public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8" );
精髓:
1)、SpringBoot启动会加载大量的自动配置类
2)、我们看我们需要的功能有没有SpringBoot默认写好的自动配置类;
3)、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)
4)、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值;
xxxxAutoConfigurartion:自动配置类;
给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
2、细节 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存在指定项
自动配置类必须在一定的条件下才能生效;
我们怎么知道哪些自动配置类生效;
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告
,这样我们就可以很方便的知道哪些自动配置类生效;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Console: ========================= CONDITIONS EVALUATION REPORT (环境评估报告) ========================= Positive matches:(自动配置类启用的) ----------------- DispatcherServletAutoConfiguration matched: - @ConditionalOnClass found required class 'org .springframework .web .servlet .DispatcherServlet ' ; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) - @ConditionalOnWebApplication (required) found StandardServletEnvironment (OnWebApplicationCondition) Negative matches:(没有启动,没有匹配成功的自动配置类) ----------------- ActiveMQAutoConfiguration: Did not match: - @ConditionalOnClass did not find required classes 'javax.jms.ConnectionFactory', 'org.apache.activemq.ActiveMQConnectionFactory' (OnClassCondition) AopAutoConfiguration: Did not match: - @ConditionalOnClass did not find required classes 'org.aspectj.lang.annotation.Aspect', 'org.aspectj.lang.reflect.Advice' (OnClassCondition)
三、日志 1、日志框架小张;开发一个大型系统;
1、System.out.println("");将关键数据打印在控制台;去掉?写在一个文件?
2、框架来记录系统的一些运行时信息;日志框架 ; zhanglogging.jar;
3、高大上的几个功能?异步模式?自动归档?xxxx? zhanglogging-good.jar?
4、将以前框架卸下来?换上新的框架,重新修改之前相关的API;zhanglogging-prefect.jar;
5、JDBC—数据库驱动;
写了一个统一的接口层;日志门面(日志的一个抽象层);logging-abstract.jar;
给项目中导入具体的日志实现就行了;我们之前的日志框架都是实现的抽象层;
市面上的日志框架;
JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j…
日志门面 (日志的抽象层) 日志实现 JCL(Jakarta Commons Logging) SLF4j(Simple Logging Facade for Java) jboss-logging Log4j JUL(java.util.logging) Log4j2 Logback
左边选一个门面(抽象层)、右边来选一个实现;
日志门面: SLF4J
;
日志实现:Logback
;
Slf4J和Logback以及log4j是同一个人写的,但是log4j版本太老
SpringBoot:底层是Spring框架,Spring框架默认是用JCL ;
SpringBoot选用 SLF4j和logback;
2、SLF4j使用以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法;
给系统里面导入slf4j的jar和 logback的实现jar
1 2 3 4 5 6 7 8 9 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" ); } }
图示
每一个日志的实现框架都有自己的配置文件。使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文件;
2、遗留问题a(slf4j+logback): Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx
统一日志记录,即使是别的框架和我一起统一使用slf4j进行输出?
如何让系统中所有的日志都统一到slf4j;
1、将系统中其他日志框架先排除出去;
2、用中间包来替换原有的日志框架;
3、我们导入slf4j其他的实现
3、SpringBoot日志关系1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter</artifactId > </dependency >
SpringBoot使用它来做日志功能;
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-logging</artifactId > </dependency >
底层依赖关系
总结 :
1)、SpringBoot底层也是使用slf4j+logback的方式进行日志记录
2)、SpringBoot也把其他的日志都替换成了slf4j ;
3)、中间替换包?
1 2 3 4 5 6 @SuppressWarnings("rawtypes") public abstract class LogFactory { static String UNSUPPORTED_OPERATION_IN_JCL_OVER_SLF4J = "http://www.slf4j.org/codes.html#unsupported_operation_in_jcl_over_slf4j" ; static LogFactory logFactory = new SLF4JLogFactory();
4)、如果我们要引入其他框架?一定要把这个框架的默认日志依赖移除掉?
Spring框架用的是commons-logging;
1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-core</artifactId > <exclusions > <exclusion > <groupId > commons-logging</groupId > <artifactId > commons-logging</artifactId > </exclusion > </exclusions > </dependency >
SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉即可;
4、日志使用; 1、默认配置SpringBoot默认帮我们配置好了日志;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Logger logger = LoggerFactory.getLogger(getClass()); @Test public void contextLoads () { logger.trace("这是trace日志..." ); logger.debug("这是debug日志..." ); logger.info("这是info日志..." ); logger.warn("这是warn日志..." ); logger.error("这是error日志..." ); }
日志输出格式: %d表示日期时间, %thread表示线程名, %-5level:级别从左显示5个字符宽度 %logger{50} 表示logger名字最长50个字符,否则按照句点分割。 %msg:日志消息, %n是换行符 –> %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
SpringBoot修改日志的默认配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 logging.level.com.atguigu =trace logging.file.path =E:\\知识学习\\01尚硅谷SpringBoot核心技术篇\\代码\\spring-boot-03-logging\\src\\main logging.pattern.console =%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n logging.pattern.file =%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} ==== %msg%n
logging.file logging.path Example Description (none) (none) ds 只在控制台输出 指定文件名 (none) my.log 输出日志到my.log文件 (none) 指定目录 /var/log 输出到指定目录的 spring.log 文件中
2、指定配置给类路径下放上每个日志框架自己的配置文件即可;SpringBoot就不使用他默认配置的了
Logging System Customization Logback logback-spring.xml, logback-spring.groovy, logback.xml or logback.groovy Log4j2 log4j2-spring.xml or log4j2.xml JDK (Java Util Logging) logging.properties
logback.xml
:直接就被日志框架识别了;
logback-spring.xml
:日志框架就不直接加载日志的配置项,由SpringBoot解析日志配置,可以使用SpringBoot的高级Profile功能
1 2 3 4 <springProfile name ="staging" > 可以指定某段配置只在某个环境下生效 </springProfile >
如(具体配置看文件夹):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <appender name ="stdout" class ="ch.qos.logback.core.ConsoleAppender" > <layout class ="ch.qos.logback.classic.PatternLayout" > <springProfile name ="dev" > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} ----> [%thread] ---> %-5level %logger{50} - %msg%n</pattern > </springProfile > <springProfile name ="!dev" > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} ==== [%thread] ==== %-5level %logger{50} - %msg%n</pattern > </springProfile > </layout > </appender >
点击查看logback-spring.xml的完整配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 <?xml version="1.0" encoding="UTF-8"?> <configuration scan ="false" scanPeriod ="60 seconds" debug ="false" > <property name ="LOG_HOME" value ="F:\\日志存放\\manager" /> <property name ="debug" value ="manager-codekiller-debug" > </property > <property name ="info" value ="manager-codekiller-info" > </property > <property name ="error" value ="manager-codekiller-error" > </property > <property name ="warn" value ="manager-codekiller-warn" > </property > <property name ="job" value ="manager-codekiller-job" > </property > <property name ="fileName" value ="logback" > </property > <appender name ="console" class ="ch.qos.logback.core.ConsoleAppender" > <layout class ="ch.qos.logback.classic.PatternLayout" > <springProfile name ="dev" > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %magenta([%-15.15(%thread)]) %cyan( %-50.50(%logger{50} )) : %msg%n</pattern > </springProfile > <springProfile name ="!dev" > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %magenta([%-15.15(%thread)]) %cyan( %-50.50(%logger{50} )) : %msg%n</pattern > </springProfile > </layout > </appender > <appender name ="consoleWarn" class ="ch.qos.logback.core.ConsoleAppender" > <layout class ="ch.qos.logback.classic.PatternLayout" > <springProfile name ="dev" > <pattern > %magenta(%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %magenta([%-15.15(%thread)]) %cyan( %-50.50(%logger{50} )) : %msg%n)</pattern > </springProfile > <springProfile name ="!dev" > <pattern > %magenta(%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %magenta([%-15.15(%thread)]) %cyan( %-50.50(%logger{50} )) : %msg%n)</pattern > </springProfile > </layout > </appender > <appender name ="consoleError" class ="ch.qos.logback.core.ConsoleAppender" > <layout class ="ch.qos.logback.classic.PatternLayout" > <springProfile name ="dev" > <pattern > %boldMagenta(%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %magenta([%-15.15(%thread)]) %cyan( %-50.50(%logger{50} )) : %msg%n)</pattern > </springProfile > <springProfile name ="!dev" > <pattern > %boldMagenta(%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %magenta([%-15.15(%thread)]) %cyan( %-50.50(%logger{50} )) : %msg%n)</pattern > </springProfile > </layout > </appender > <appender name ="debug" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <file > ${LOG_HOME}/${debug}/${fileName}.log</file > <filter class ="ch.qos.logback.classic.filter.LevelFilter" > <level > DEBUG</level > <onMatch > ACCEPT</onMatch > <onMismatch > DENY</onMismatch > </filter > <rollingPolicy class ="ch.qos.logback.core.rolling.TimeBasedRollingPolicy" > <fileNamePattern > ${LOG_HOME}/${debug}/${fileName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern > <MaxHistory > 365</MaxHistory > <timeBasedFileNamingAndTriggeringPolicy class ="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP" > <maxFileSize > 100MB</maxFileSize > </timeBasedFileNamingAndTriggeringPolicy > </rollingPolicy > <layout class ="ch.qos.logback.classic.PatternLayout" > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%-15.15(%thread)] %-50.50(%logger{50}) : %msg%n</pattern > </layout > </appender > <appender name ="info" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <file > ${LOG_HOME}/${info}/${fileName}.log</file > <filter class ="ch.qos.logback.classic.filter.LevelFilter" > <level > INFO</level > <onMatch > ACCEPT</onMatch > <onMismatch > DENY</onMismatch > </filter > <rollingPolicy class ="ch.qos.logback.core.rolling.TimeBasedRollingPolicy" > <fileNamePattern > ${LOG_HOME}/${info}/${fileName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern > <MaxHistory > 365</MaxHistory > <timeBasedFileNamingAndTriggeringPolicy class ="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP" > <maxFileSize > 100MB</maxFileSize > </timeBasedFileNamingAndTriggeringPolicy > </rollingPolicy > <layout class ="ch.qos.logback.classic.PatternLayout" > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%-15.15(%thread)] %-50.50(%logger{50}) : %msg%n</pattern > </layout > </appender > <appender name ="warn" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <file > ${LOG_HOME}/${warn}/${fileName}.log</file > <filter class ="ch.qos.logback.classic.filter.LevelFilter" > <level > WARN</level > <onMatch > ACCEPT</onMatch > <onMismatch > DENY</onMismatch > </filter > <rollingPolicy class ="ch.qos.logback.core.rolling.TimeBasedRollingPolicy" > <fileNamePattern > ${LOG_HOME}/${warn}/${fileName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern > <MaxHistory > 365</MaxHistory > <timeBasedFileNamingAndTriggeringPolicy class ="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP" > <maxFileSize > 100MB</maxFileSize > </timeBasedFileNamingAndTriggeringPolicy > </rollingPolicy > <layout class ="ch.qos.logback.classic.PatternLayout" > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%-15.15(%thread)] %-50.50(%logger{50}) : %msg%n</pattern > </layout > </appender > <appender name ="error" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <file > ${LOG_HOME}/${error}/${fileName}.log</file > <filter class ="ch.qos.logback.classic.filter.LevelFilter" > <level > ERROR</level > <onMatch > ACCEPT</onMatch > <onMismatch > DENY</onMismatch > </filter > <rollingPolicy class ="ch.qos.logback.core.rolling.TimeBasedRollingPolicy" > <fileNamePattern > ${LOG_HOME}/${error}/${fileName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern > <MaxHistory > 365</MaxHistory > <timeBasedFileNamingAndTriggeringPolicy class ="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP" > <maxFileSize > 100MB</maxFileSize > </timeBasedFileNamingAndTriggeringPolicy > </rollingPolicy > <layout class ="ch.qos.logback.classic.PatternLayout" > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%-15.15(%thread)] %-50.50(%logger{50}) : %msg%n</pattern > </layout > </appender > <appender name ="job" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <file > ${LOG_HOME}/${job}/${fileName}.log</file > <rollingPolicy class ="ch.qos.logback.core.rolling.TimeBasedRollingPolicy" > <fileNamePattern > ${LOG_HOME}/${job}/${fileName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern > <MaxHistory > 365</MaxHistory > <timeBasedFileNamingAndTriggeringPolicy class ="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP" > <maxFileSize > 100MB</maxFileSize > </timeBasedFileNamingAndTriggeringPolicy > </rollingPolicy > <layout class ="ch.qos.logback.classic.PatternLayout" > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%-15.15(%thread)] %-50.50(%logger{50}) : %msg%n</pattern > </layout > </appender > <logger name ="top.codekiller.manager.examination.job" level ="debug" additivity ="false" > <appender-ref ref ="job" > </appender-ref > <appender-ref ref ="console" > </appender-ref > </logger > <logger name ="top.codekiller.manager.examination" level ="debug" additivity ="false" > <appender-ref ref ="console" /> <appender-ref ref ="debug" /> </logger > <logger name ="top.codekiller.manager.examination" level ="warn" additivity ="false" > <appender-ref ref ="consoleWarn" /> <appender-ref ref ="warn" /> </logger > <logger name ="top.codekiller.manager.examination" level ="error" additivity ="false" > <appender-ref ref ="consoleError" /> <appender-ref ref ="error" /> </logger > <root level ="info" > <appender-ref ref ="console" /> <appender-ref ref ="debug" /> <appender-ref ref ="info" /> <appender-ref ref ="warn" /> <appender-ref ref ="error" /> </root > </configuration >
如果使用logback.xml作为日志配置文件,还要使用profile功能,会有以下错误
1 no applicable action for [springProfile]
5、切换日志框架可以按照slf4j的日志适配图,进行相关的切换;
slf4j+log4j的方式;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <dependency > <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 > </dependency > <dependency > <groupId > org.slf4j</groupId > <artifactId > slf4j-log4j12</artifactId > </dependency >
切换为log4j2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <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 >
四、Web开发 1、简介使用SpringBoot;
1)、创建SpringBoot应用,选中我们需要的模块;
2)、SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来
3)、自己编写业务代码;
自动配置原理?
这个场景SpringBoot帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?xxx
xxxxAutoConfiguration:帮我们给容器中自动配置组件;
xxxxProperties:配置类来封装配置文件的内容;
web自动配置规则WebMvcAutoConfiguration WebMvcProperties ViewResolver自动配置 静态资源自动映射 Formatter与Converter自动配置 HttpMessageConverter自动配置 静态首页 favicon 错误处理 2、SpringBoot对静态资源的映射规则;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 WebMvcAuotConfiguration.java: @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)); } }
1 2 3 4 5 6 7 8 9 10 一个静态内部类 @Configuration(proxyBeanMethods = false) @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class }) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer { private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class); private final ResourceProperties resourceProperties;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false) public class ResourceProperties { private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/" , "classpath:/resources/" , "classpath:/static/" , "classpath:/public/" }; private String[] staticLocations; private boolean addMappings; private final ResourceProperties.Chain chain; private final ResourceProperties.Cache cache; public ResourceProperties () { this .staticLocations = CLASSPATH_RESOURCE_LOCATIONS; this .addMappings = true ; this .chain = new ResourceProperties.Chain(); this .cache = new ResourceProperties.Cache(); }
1)、所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找资源;
webjars:以jar包的方式引入静态资源;
http://www.webjars.org/
localhost:8080/webjars/jquery/3.3.1/jquery.js
1 2 3 4 5 6 7 在访问的时候只需要写webjars下面资源的名称即可 <dependency > <groupId > org.webjars</groupId > <artifactId > jquery</artifactId > <version > 3.3.1</version > </dependency >
2)、"/**" 访问当前项目的任何资源,都去(静态资源的文件夹)找映射
“classpath:/META-INF/resources/”, “classpath:/resources/”, “classpath:/static/”, “classpath:/public/” “/”:当前项目的根路径 localhost:8080/abc === 去静态资源文件夹里面找abc 3)、欢迎页; 静态资源文件夹下的所有index.html页面;被"/**"映射;
localhost:8080/ 找index页面
location就是静态资源路径,所以欢迎页的页面就是上面静态资源下的index.html,被/**映射,因此直接访问项目就是访问欢迎页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 还是在WebMvcAutoConfiguration.java中 @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping (ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping( new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), this .mvcProperties.getStaticPathPattern()); welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); return welcomePageHandlerMapping; } private Optional<Resource> getWelcomePage () { String[] locations = getResourceLocations(this .resourceProperties.getStaticLocations()); return Arrays.stream(locations).map(this ::getIndexHtml).filter(this ::isReadable).findFirst(); } private Resource getIndexHtml (String location) { return this .resourceLoader.getResource(location + "index.html" ); } WelcomePageHandlerMapping.java中 WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) { if (welcomePage.isPresent() && "/**" .equals(staticPathPattern)) { logger.info("Adding welcome page: " + welcomePage.get()); setRootViewName("forward:index.html" ); } else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) { logger.info("Adding welcome page template: index" ); setRootViewName("index" ); } }
4)、所有的 **/favicon.ico 都是在静态资源文件下找;
3、模板引擎JSP、Velocity、Freemarker、Thymeleaf
SpringBoot推荐的Thymeleaf;
语法更简单,功能更强大;
1、引入thymeleaf;1 2 3 4 5 6 7 8 9 10 11 12 <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 >
2、Thymeleaf使用1 2 3 4 5 6 7 8 9 10 11 @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就能自动渲染;
使用:
1、导入thymeleaf的名称空间1 <html lang ="en" xmlns:th ="http://www.thymeleaf.org" >
2、使用thymeleaf语法;1 2 3 4 5 6 7 8 9 10 11 12 <!DOCTYPE html > <html lang ="en" xmlns:th ="http://www.thymeleaf.org" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <h1 > 成功!</h1 > <div th:text ="${hello}" > 这是显示欢迎信息</div > </body > </html >
3、语法规则1)、th:text;改变当前元素里面的文本内容;
th:任意html属性;来替换原生属性的值
2)、表达式?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 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: + , - , * , / , % Minus sign (unary operator): - Boolean operations:(布尔运算) Binary operators: and , or Boolean negation (unary operator): ! , not Comparisons and equality:(比较运算) Comparators: > , < , >= , <= ( gt , lt , ge , le ) Equality operators: == , != ( eq , ne ) Conditional operators:条件运算(三元运算符) If-then: (if) ? (then) If-then-else: (if) ? (then) : (else) Default: (value) ?: (defaultvalue) Special tokens: No-Operation: _
4、SpringMVC自动配置https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/reference/htmlsingle/#boot-features-developing-web-applications
1. Spring MVC auto-configurationSpring Boot 自动配置好了SpringMVC
以下是SpringBoot对SpringMVC的默认配置:(WebMvcAutoConfiguration)
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染(转发?重定向?)) ContentNegotiatingViewResolver:组合所有的视图解析器的; 如何定制:我们可以自己给容器中添加一个视图解析器;自动的将其组合进来;
Support for serving static resources, including support for WebJars (see below).静态资源文件夹路径,webjars
Static index.html support. 静态首页访问
Custom Favicon support (see below). favicon.ico
自动注册了 of Converter, GenericConverter, Formatter beans.
Converter:转换器; public String hello(User user):类型转换使用Converter Formatter 格式化器; 2017.12.17===Date; 1 2 3 4 5 @Bean @ConditionalOnProperty(prefix = "spring.mvc", name = "date-format") public Formatter<Date> 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).定义错误代码生成规则
Automatic use of a ConfigurableWebBindingInitializer bean (see below).我们可以配置一个ConfigurableWebBindingInitializer来替换默认的;(添加到容器)
初始化WebDataBinder; 请求数据=====JavaBean;
org.springframework.boot.autoconfigure.web :web的所有自动场景;
If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc .
If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.
2、扩展SpringMVC1 2 3 4 5 6 7 <mvc:view-controller path ="/hello" view-name ="success" /> <mvc:interceptors > <mvc:interceptor > <mvc:mapping path ="/hello" /> <bean > </bean > </mvc:interceptor > </mvc:interceptors >
编写一个配置类(@Configuration),WebMvcConfigurer;不能标注@EnableWebMvc
;
既保留了所有的自动配置,也能用我们扩展的配置;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Configuration public class MyMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers (ViewControllerRegistry registry) { registry.addRedirectViewController("/viewhello" ,"index.html" ); } @Bean public WebMvcConfigurer webMvcConfigurerAdapter () { WebMvcConfigurer webMvcConfigurer=new WebMvcConfigurer() { @Override public void addViewControllers (ViewControllerRegistry registry) { registry.addViewController("/" ).setViewName("index" ); registry.addViewController("index.html" ).setViewName("index" ); registry.addViewController("/main.html" ).setViewName("dashboard" ); } @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**" ).excludePathPatterns("/index.html" ,"/" ,"/user/login" , "/asserts/**" ,"/webjars/**" ); } }; return webMvcConfigurer; } }
原理:
1)、WebMvcAutoConfiguration是SpringMVC的自动配置类
2)、在做其他自动配置时会导入;@Import(EnableWebMvcConfiguration.class)
3)、容器中所有的WebMvcConfigurer都会一起起作用;
4)、我们的配置类也会被调用;
效果:SpringMVC的自动配置和我们的扩展配置都会起作用;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 WebMvcAutoConfiguration.java中的一个内部类: @Configuration(proxyBeanMethods = false) @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class }) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer { --------------------------------------------------------------------------- EnableWebMvcConfiguration.java @Configuration public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration { --------------------------------------------------------------------------- DelegatingWebMvcConfiguration.java @Configuration(proxyBeanMethods = false) public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); @Autowired(required = false) public void setConfigurers (List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { this .configurers.addWebMvcConfigurers(configurers); } } } ---------------------------------------------------------------------------- class WebMvcConfigurerComposite implements WebMvcConfigurer { private final List<WebMvcConfigurer> delegates = new ArrayList(); public void addViewControllers (ViewControllerRegistry registry) { Iterator var2 = this .delegates.iterator(); while (var2.hasNext()) { WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next(); delegate.addViewControllers(registry); } } ....... public void addResourceHandlers (ResourceHandlerRegistry registry) {.....} public void addViewControllers (ViewControllerRegistry registry) { Iterator var2 = this .delegates.iterator(); while (var2.hasNext()) { WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next(); delegate.addViewControllers(registry); } } ..........
3、全面接管SpringMVC;SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己配置;所有的SpringMVC的自动配置都失效了
我们需要在配置类中添加@EnableWebMvc即可;
1 2 3 4 5 6 7 8 9 10 11 12 @EnableWebMvc @Configuration public class MyMvcConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers (ViewControllerRegistry registry) { registry.addViewController("/atguigu" ).setViewName("success" ); } }
原理:
为什么@EnableWebMvc自动配置就失效了;
1)@EnableWebMvc的核心
1 2 @Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc {
2)、
1 2 @Configuration public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
3)、
1 2 3 4 5 6 7 8 9 10 @Configuration @ConditionalOnWebApplication @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class }) public class WebMvcAutoConfiguration {
4)、@EnableWebMvc将WebMvcConfigurationSupport组件导入进来;
5)、导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能;
5、如何修改SpringBoot的默认配置模式 :
1)、SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默认的组合起来;
2)、在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置
3)、在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置
6、RestfulCRUD注意: 1.在html页面中,若想访问私有的静态文件,要么需要通过一个controller再进行转发,要么配置>view-controller 2.注意注册或者删除等带参数的操作url会带参数,在controller中进行跳转的时候尽量使用重定向 3.在html中可以抽取出相同部分,通过th:fragment="" 或者id,通过replace,insert或者include进行调用 4.在使用each生成一些使用删除或者更新的按钮时,需要使用form表单进行rest风格操作,为了防止产生过多的form表单,可以使用单击事件监听,通过th:attr(x=xxx)获得属性传给放在其他地方的form表单并且实现submmit。
1)、默认访问首页1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 方式一: @RequestMapping({"/","index.html"}) @Configuration public class MyMvcConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers (ViewControllerRegistry registry) { registry.addViewController("/atguigu" ).setViewName("success" ); } @Bean public WebMvcConfigurerAdapter webMvcConfigurerAdapter () { WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() { @Override public void addViewControllers (ViewControllerRegistry registry) { registry.addViewController("/" ).setViewName("login" ); registry.addViewController("/index.html" ).setViewName("login" ); } }; return adapter; } }
2)、国际化1)、编写国际化配置文件;
2)、使用ResourceBundleMessageSource管理国际化资源文件
3)、在页面使用fmt:message取出国际化内容
步骤 :
1)、编写国际化配置文件,抽取页面需要显示的国际化消息
2)、SpringBoot自动配置好了管理国际化资源文件的组件:MessageSourceAutoConfiguration
;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @ConfigurationProperties(prefix = "spring.messages") public class MessageSourceAutoConfiguration { private String basename = "messages" ; @Bean public MessageSource messageSource () { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); if (StringUtils.hasText(this .basename)) { messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray( StringUtils.trimAllWhitespace(this .basename))); } if (this .encoding != null ) { messageSource.setDefaultEncoding(this .encoding.name()); } messageSource.setFallbackToSystemLocale(this .fallbackToSystemLocale); messageSource.setCacheSeconds(this .cacheSeconds); messageSource.setAlwaysUseMessageFormat(this .alwaysUseMessageFormat); return messageSource; }
3)、去页面获取国际化的值;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <!DOCTYPE html > <html lang ="en" xmlns:th ="http://www.thymeleaf.org" > <head > <meta http-equiv ="Content-Type" content ="text/html; 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 ="" > <title > Signin Template for Bootstrap</title > <link href ="asserts/css/bootstrap.min.css" th:href ="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel ="stylesheet" > <link href ="asserts/css/signin.css" th:href ="@{/asserts/css/signin.css}" rel ="stylesheet" > </head > <body class ="text-center" > <form class ="form-signin" action ="dashboard.html" > <img class ="mb-4" th:src ="@{/asserts/img/bootstrap-solid.svg}" src ="asserts/img/bootstrap-solid.svg" alt ="" width ="72" height ="72" > <h1 class ="h3 mb-3 font-weight-normal" th:text ="#{login.tip}" > Please sign in</h1 > <label class ="sr-only" th:text ="#{login.username}" > Username</label > <input type ="text" class ="form-control" placeholder ="Username" th:placeholder ="#{login.username}" required ="" autofocus ="" > <label class ="sr-only" th:text ="#{login.password}" > Password</label > <input type ="password" class ="form-control" placeholder ="Password" th:placeholder ="#{login.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" type ="submit" th:text ="#{login.btn}" > Sign in</button > <p class ="mt-5 mb-3 text-muted" > © 2017-2018</p > <a class ="btn btn-sm" > 中文</a > <a class ="btn btn-sm" > English</a > </form > </body > </html >
效果:根据浏览器语言设置的信息切换了国际化;
原理:
国际化Locale
(区域信息对象);LocaleResolver
(获取区域信息对象);
1 2 3 4 5 6 7 8 9 10 11 12 @Bean @ConditionalOnMissingBean @ConditionalOnProperty(prefix = "spring.mvc", name = "locale") public LocaleResolver localeResolver () { if (this .mvcProperties .getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this .mvcProperties.getLocale()); } AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); localeResolver.setDefaultLocale(this .mvcProperties.getLocale()); return localeResolver; }
默认的就是根据请求头带来的区域信息获取Locale进行国际化
4)、点击链接切换国际化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class MyLocaleResolver implements LocaleResolver { @Override public Locale resolveLocale (HttpServletRequest request) { String l = request.getParameter("l" ); Locale locale = Locale.getDefault(); 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) { } }
1 2 3 4 5 @Bean public LocaleResolver localeResolver () { return new MyLocaleResolver(); } }
3)、登陆开发期间模板引擎页面修改以后,要实时生效
1)、禁用模板引擎的缓存
1 2 # 禁用缓存 spring.thymeleaf.cache=false
2)、页面修改完成以后ctrl+f9:重新编译;
登陆错误消息的显示
1 <p style ="color: red" th:text ="${msg}" th:if ="${not #strings.isEmpty(msg)}" > </p >
4)、拦截器进行登陆检查拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class LoginHandlerInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user = request.getSession().getAttribute("loginUser" ); if (user == 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Bean public WebMvcConfigurer webMvcConfigurerAdapter () { WebMvcConfigurer webMvcConfigurer=new WebMvcConfigurer() { @Override public void addViewControllers (ViewControllerRegistry registry) { registry.addViewController("/" ).setViewName("index" ); registry.addViewController("index.html" ).setViewName("index" ); registry.addViewController("/main.html" ).setViewName("dashboard" ); } @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**" ).excludePathPatterns("/index.html" ,"/" ,"/user/login" , "/asserts/**" ,"/webjars/**" ); } }; return webMvcConfigurer; }
5)、CRUD-员工列表实验要求:
1)、RestfulCRUD:CRUD满足Rest风格;
URI: /资源名称/资源标识 HTTP请求方式区分对资源CRUD操作
操作 普通CRUD(uri来区分操作) RestfulCRUD 查询 getEmp emp—GET 添加 addEmp?xxx emp—POST 修改 updateEmp?id=xxx&xxx=xx emp/{id}—PUT 删除 deleteEmp?id=1 emp/{id}—DELETE
2)、实验的请求架构;
实验功能 请求URI 请求方式 查询所有员工 emps GET 查询某个员工(来到修改页面) emp/1 GET 来到添加页面 emp GET 添加员工 emp POST 来到修改页面(查出员工进行信息回显) emp/1 GET 修改员工 emp PUT 删除员工 emp/1 DELETE
3)、员工列表:
thymeleaf公共页面元素抽取
1、抽取公共片段
1 2 3 <div th:fragment ="copy" > © 2011 The Good Thymes Virtual Grocery</div >
2、引入公共片段
1 2 3 <div th:insert ="~{footer :: copy}" > </div > ~{templatename::selector}:模板名::选择器(或者通过公共部分的id:#id) ~{templatename::fragmentname}:模板名::片段名
3、默认效果:
insert的公共片段在div标签中
如果使用th:insert等属性进行引入,可以不用写~{}:
行内写法可以加上:[[~{}]];
三种引入公共片段的th属性:
th:insert
:将公共片段整个插入到声明引入的元素中
th:replace
:将声明引入的元素替换为公共片段
th:include
:将被引入的片段的内容包含进这个标签中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <footer th:fragment ="copy" > © 2011 The Good Thymes Virtual Grocery</footer > 引入方式 <div th:insert ="footer :: copy" > </div > <div th:replace ="footer :: copy" > </div > <div th:include ="footer :: copy" > </div > 效果 <div > <footer > © 2011 The Good Thymes Virtual Grocery </footer > </div > <footer > © 2011 The Good Thymes Virtual Grocery</footer > <div > © 2011 The Good Thymes Virtual Grocery</div >
引入片段的时候传入参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <nav class ="col-md-2 d-none d-md-block bg-light sidebar" id ="sidebar" > <div class ="sidebar-sticky" > <ul class ="nav flex-column" > <li class ="nav-item" > <a class ="nav-link active" th:class ="${activeUri=='main.html'?'nav-link active':'nav-link'}" href ="#" th:href ="@{/main.html}" > <svg xmlns ="http://www.w3.org/2000/svg" width ="24" height ="24" viewBox ="0 0 24 24" fill ="none" stroke ="currentColor" stroke-width ="2" stroke-linecap ="round" stroke-linejoin ="round" class ="feather feather-home" > <path d ="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" > </path > <polyline points ="9 22 9 12 15 12 15 22" > </polyline > </svg > Dashboard <span class ="sr-only" > (current)</span > </a > </li >
1 2 <div th:replace ="commons/bar::#sidebar(activeUri='emps')" > </div >
6)、CRUD-员工添加添加页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <form > <div class ="form-group" > <label > LastName</label > <input type ="text" class ="form-control" placeholder ="zhangsan" > </div > <div class ="form-group" > <label > Email</label > <input type ="email" class ="form-control" placeholder ="zhangsan@atguigu.com" > </div > <div class ="form-group" > <label > Gender</label > <br /> <div class ="form-check form-check-inline" > <input class ="form-check-input" type ="radio" name ="gender" value ="1" > <label class ="form-check-label" > 男</label > </div > <div class ="form-check form-check-inline" > <input class ="form-check-input" type ="radio" name ="gender" value ="0" > <label class ="form-check-label" > 女</label > </div > </div > <div class ="form-group" > <label > department</label > <select class ="form-control" > <option > 1</option > <option > 2</option > <option > 3</option > <option > 4</option > <option > 5</option > </select > </div > <div class ="form-group" > <label > Birth</label > <input type ="text" class ="form-control" placeholder ="zhangsan" > </div > <button type ="submit" class ="btn btn-primary" > 添加</button > </form >
提交的数据格式不对:生日:日期;
2017-12-12;2017/12/12;2017.12.12;
日期的格式化;SpringMVC将页面提交的值需要转换为指定的类型;
2017-12-12—Date; 类型转换,格式化;
默认日期是按照/的方式;
7)、CRUD-员工修改修改添加二合一表单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <form th:action ="@{/emp}" method ="post" > <input type ="hidden" name ="_method" value ="put" th:if ="${emp!=null}" /> <input type ="hidden" name ="id" th:if ="${emp!=null}" th:value ="${emp.id}" > <div class ="form-group" > <label > LastName</label > <input name ="lastName" type ="text" class ="form-control" placeholder ="zhangsan" th:value ="${emp!=null}?${emp.lastName}" > </div > <div class ="form-group" > <label > Email</label > <input name ="email" type ="email" class ="form-control" placeholder ="zhangsan@atguigu.com" th:value ="${emp!=null}?${emp.email}" > </div > <div class ="form-group" > <label > Gender</label > <br /> <div class ="form-check form-check-inline" > <input class ="form-check-input" type ="radio" name ="gender" value ="1" th:checked ="${emp!=null}?${emp.gender==1}" > <label class ="form-check-label" > 男</label > </div > <div class ="form-check form-check-inline" > <input class ="form-check-input" type ="radio" name ="gender" value ="0" th:checked ="${emp!=null}?${emp.gender==0}" > <label class ="form-check-label" > 女</label > </div > </div > <div class ="form-group" > <label > department</label > <select class ="form-control" name ="department.id" > <option th:selected ="${emp!=null}?${dept.id == emp.department.id}" th:value ="${dept.id}" th:each ="dept:${depts}" th:text ="${dept.departmentName}" > 1</option > </select > </div > <div class ="form-group" > <label > Birth</label > <input name ="birth" type ="text" class ="form-control" placeholder ="zhangsan" th:value ="${emp!=null}?${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}" > </div > <button type ="submit" class ="btn btn-primary" th:text ="${emp!=null}?'修改':'添加'" > 添加</button > </form >
8)、CRUD-员工删除1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <tr th:each ="emp:${emps}" > <td th:text ="${emp.id}" > </td > <td > [[${emp.lastName}]]</td > <td th:text ="${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 > <td > <a class ="btn btn-sm btn-primary" th:href ="@{/emp/}+${emp.id}" > 编辑</a > <button th:attr ="del_uri=@{/emp/}+${emp.id}" class ="btn btn-sm btn-danger deleteBtn" > 删除</button > </td > </tr > <script > $(".deleteBtn" ).click(function ( ) { $("#deleteEmpForm" ).attr("action" ,$(this ).attr("del_uri" )).submit(); return false ; }); </script >
7、错误处理机制1)、SpringBoot默认的错误处理机制
默认效果:
1)、浏览器,返回一个默认的错误页面,注意看发送请求的请求头:
2)、如果是其他客户端,默认响应一个json数据
原理 :
查看org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration源码,
这里是springboot错误处理的自动配置信息
主要给日容器中注册了以下组件:
ErrorPageCustomizer 系统出现错误以后来到error请求进行处理;相当于(web.xml注册的错误页面规则) BasicErrorController 处理/error请求 DefaultErrorViewResolver 默认的错误视图解析器 DefaultErrorAttributes 错误信息 defaultErrorView 默认错误视图 ErrorPageCustomizer
1 2 3 4 @Bean public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer (DispatcherServletPath dispatcherServletPath) { return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this .serverProperties, dispatcherServletPath); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private static class ErrorPageCustomizer implements ErrorPageRegistrar , Ordered { private final ServerProperties properties; private final DispatcherServletPath dispatcherServletPath; protected ErrorPageCustomizer (ServerProperties properties, DispatcherServletPath dispatcherServletPath) { this .properties = properties; this .dispatcherServletPath = dispatcherServletPath; } public void registerErrorPages (ErrorPageRegistry errorPageRegistry) { ErrorPage errorPage = new ErrorPage(this .dispatcherServletPath.getRelativePath(this .properties.getError().getPath())); errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage}); } public int getOrder () { return 0 ; } }
当请求出现错误后就会转发到/error
然后这个error请求就会被BasicErrorController处理;
BasicErrorController
:处理默认/error请求1 2 3 4 5 6 7 8 @Bean @ConditionalOnMissingBean( value = {ErrorController.class}, search = SearchStrategy.CURRENT ) public BasicErrorController basicErrorController (ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) { return new BasicErrorController(errorAttributes, this .serverProperties.getError(), (List)errorViewResolvers.orderedStream().collect(Collectors.toList())); }
处理/error请求
1 2 3 4 5 6 7 8 9 Controller @RequestMapping({"${server.error.path:${error.path:/error}}"}) public class BasicErrorController extends AbstractErrorController
这两个方法一个用于浏览器请求响应html页面,一个用于其他客户端请求响应json数据
处理浏览器请求的方法 中,modelAndView存储到哪个页面的页面地址和页面内容数据
看一下调用的resolveErrorView方法
1 2 3 4 5 6 7 8 9 10 11 AbstractErrorController.java: protected ModelAndView resolveErrorView (HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { for (ErrorViewResolver resolver : this .errorViewResolvers) { ModelAndView modelAndView = resolver.resolveErrorView(request, status, model); if (modelAndView != null ) { return modelAndView; } } return null ; }
ErrorViewResolver从哪里来的呢?
已经在容器中注册了一个DefaultErrorViewResolver
DefaultErrorViewResolver
:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Configuration(proxyBeanMethods = false) static class DefaultErrorViewResolverConfiguration { private final ApplicationContext applicationContext; private final ResourceProperties resourceProperties; DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext, ResourceProperties resourceProperties) { this .applicationContext = applicationContext; this .resourceProperties = resourceProperties; } @Bean @ConditionalOnBean(DispatcherServlet.class) @ConditionalOnMissingBean(ErrorViewResolver.class) DefaultErrorViewResolver conventionErrorViewResolver () { return new DefaultErrorViewResolver(this .applicationContext, this .resourceProperties); } }
然后调用ErrorViewResolver的resolveErrorView()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public ModelAndView resolveErrorView (HttpServletRequest request, HttpStatus status, Map<String, Object> model) { ModelAndView modelAndView = this .resolve(String.valueOf(status.value()), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = this .resolve((String)SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve (String viewName, Map<String, Object> model) { String errorViewName = "error/" + viewName; TemplateAvailabilityProvider provider = this .templateAvailabilityProviders.getProvider(errorViewName, this .applicationContext); if (provider != null ) { return new ModelAndView(errorViewName, model); } return resolveResource(errorViewName, model); }
如果模板引擎不可用,就调用resolveResource方法获取视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private ModelAndView resolveResource (String viewName, Map<String, Object> model) { for (String location : this .resourceProperties.getStaticLocations()) { try { Resource resource = this .applicationContext.getResource(location); resource = resource.createRelative(viewName + ".html" ); if (resource.exists()) { return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model); } } catch (Exception var8) { } } return null ; }
DefaultErrorAttributes
页面能获取的信息;
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里
步骤:一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error请求;就会被BasicErrorController处理;
1)响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的;1 2 3 4 5 6 7 8 9 10 11 12 protected ModelAndView resolveErrorView (HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { for (ErrorViewResolver resolver : this .errorViewResolvers) { ModelAndView modelAndView = resolver.resolveErrorView(request, status, model); if (modelAndView != null ) { return modelAndView; } } return null ; }
2)、如果定制错误响应: 如何定制错误的页面 1)、看模板引擎下有没有error/状态码文件; 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;
我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);
页面能获取的信息;
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里
2)、没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找; 3).还没有,就去找4xx和5xx的页面 4)、以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;
如何定制错误的json数据1)、自定义异常处理&返回定制json数据;(无自适应效果,不推荐)
1 2 3 4 5 6 7 8 9 10 11 12 13 @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; } }
2)、转发到/error进行自适应响应效果处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @ExceptionHandler(UserNotExistException.class) public String handleException (Exception e, HttpServletRequest request) { Map<String,Object> map = new HashMap<>(); request.setAttribute("javax.servlet.error.status_code" ,500 ); map.put("code" ,"user.notexist" ); map.put("message" ,e.getMessage()); return "forward:/error" ; }
3)、将我们的定制数据携带出去;
出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法);
1、完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;
2、页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;
容器中DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;
自定义ErrorAttributes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Component public class MyErrorAttributes extends DefaultErrorAttributes { @Override public Map<String, Object> getErrorAttributes (RequestAttributes requestAttributes, boolean includeStackTrace) { Map<String,Object> map=super .getErrorAttributes(webRequest, includeStackTrace); map.put("company" ,"atguigu" ); Map<String,Object> ext=(Map<String,Object>)webRequest.getAttribute("ext" ,0 ); map.put("ext" ,ext); return map; } }
最终的效果:响应是自适应的,可以通过定制ErrorAttributes改变需要返回的内容,
8、配置嵌入式Servlet容器SpringBoot默认使用Tomcat作为嵌入式的Servlet容器;
问题?
1、如何定制和修改Servlet容器的相关配置;1、修改和server有关的配置(ServerProperties【也是EmbeddedServletContainerCustomizer】);
1 2 3 4 5 6 7 8 9 server.port =8081 server.context-path =/crud server.tomcat.uri-encoding =UTF-8 //通用的Servlet容器设置 server.xxx //Tomcat的设置 server.tomcat.xxx
2、WebServerFactoryCustomizer
:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置
1 2 3 4 5 6 7 8 9 10 11 @Bean public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer () { return new EmbeddedServletContainerCustomizer() { @Override public void customize (ConfigurableEmbeddedServletContainer container) { container.setPort(8083 ); } }; }
2、注册Servlet三大组件【Servlet、Filter、Listener】由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。
注册三大组件用以下方式
ServletRegistrationBean
1 2 3 4 5 6 @Bean public ServletRegistrationBean myServlet () { ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet" ); return registrationBean; }
FilterRegistrationBean
1 2 3 4 5 6 7 @Bean public FilterRegistrationBean myFilter () { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(new MyFilter()); registrationBean.setUrlPatterns(Arrays.asList("/hello" ,"/myServlet" )); return registrationBean; }
ServletListenerRegistrationBean
1 2 3 4 5 @Bean public ServletListenerRegistrationBean myListener () { ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener()); return registrationBean; }
SpringBoot帮我们自动SpringMVC的时候,自动的注册SpringMVC的前端控制器;DIspatcherServlet;
DispatcherServletAutoConfiguration
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @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()); registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup( this .webMvcProperties.getServlet().getLoadOnStartup()); if (this .multipartConfig != null ) { registration.setMultipartConfig(this .multipartConfig); } return registration; }
SpringBoot能不能支持其他的Servlet容器;
3、替换为其他嵌入式Servlet容器默认支持:
Tomcat(默认使用)
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > 引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器; </dependency >
Jetty
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <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 > <dependency > <artifactId > spring-boot-starter-jetty</artifactId > <groupId > org.springframework.boot</groupId > </dependency >
Undertow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <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 > <dependency > <artifactId > spring-boot-starter-undertow</artifactId > <groupId > org.springframework.boot</groupId > </dependency >
4、嵌入式Servlet容器自动配置原理;查看web容器自动配置类
2.0以下是:EmbeddedServletContainerAutoConfiguration
ServletWebServerFactoryAutoConfiguration
:嵌入式的web服务器自动配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Configuration( proxyBeanMethods = false ) @AutoConfigureOrder(-2147483648) @ConditionalOnClass({ServletRequest.class}) @ConditionalOnWebApplication( type = Type.SERVLET ) @EnableConfigurationProperties({ServerProperties.class}) @Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, //导入BeanPostProcessorsRegistrar:Spring注解版;给容器中导入一些组件 //导入了EmbeddedServletContainerCustomizerBeanPostProcessor:(具体看下方) //后置处理器:bean初始化前后(创建完对象,还没赋值赋值)执行初始化工作 EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class}) public class ServletWebServerFactoryAutoConfiguration {
EmbeddedTomcat.class
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Configuration( proxyBeanMethods = false ) @ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class}) @ConditionalOnMissingBean( value = {ServletWebServerFactory.class}, search = SearchStrategy.CURRENT ) static class EmbeddedTomcat { TomcatServletWebServerFactory tomcatServletWebServerFactory ( ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers, ObjectProvider<TomcatContextCustomizer> contextCustomizers, ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); factory.getTomcatConnectorCustomizers() .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList())); factory.getTomcatContextCustomizers() .addAll(contextCustomizers.orderedStream().collect(Collectors.toList())); factory.getTomcatProtocolHandlerCustomizers() .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList())); return factory; } }
ServletWebServerFactory
:嵌入式的web服务器工厂
1 2 3 4 5 @FunctionalInterface public interface ServletWebServerFactory { WebServer getWebServer (ServletContextInitializer... initializers) ; }
工厂实现类
WebServer:嵌入式的web服务器实现
以TomcatServletWebServerFactory
为例,下面是TomcatServletWebServerFactory类通过把一些配置信息写在tomcat.class中,创建TomcatWebServer服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public WebServer getWebServer (ServletContextInitializer... initializers) { if (this .disableMBeanRegistry) { Registry.disableRegistry(); } Tomcat tomcat = new Tomcat(); File baseDir = this .baseDirectory != null ? this .baseDirectory : this .createTempDir("tomcat" ); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this .protocol); connector.setThrowOnFailure(true ); tomcat.getService().addConnector(connector); this .customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false ); this .configureEngine(tomcat.getEngine()); Iterator var5 = this .additionalTomcatConnectors.iterator(); while (var5.hasNext()) { Connector additionalConnector = (Connector)var5.next(); tomcat.getService().addConnector(additionalConnector); } this .prepareContext(tomcat.getHost(), initializers); return this .getTomcatWebServer(tomcat); } protected TomcatWebServer getTomcatWebServer (Tomcat tomcat) { return new TomcatWebServer(tomcat, getPort() >= 0 ); }
5、我们对嵌入式容器的配置修改是怎么生效?1 2 3 4 5 6 7 8 @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class }) public class ServletWebServerFactoryAutoConfiguration { .......
BeanPostProcessorsRegistrar
:后置处理器注册器(也是给容器注入一些组件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar , BeanFactoryAware { private ConfigurableListableBeanFactory beanFactory; public BeanPostProcessorsRegistrar () {...} public void setBeanFactory (BeanFactory beanFactory) throws BeansException {...} public void registerBeanDefinitions (AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (this .beanFactory != null ) { this .registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor" , WebServerFactoryCustomizerBeanPostProcessor.class); this .registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor" , ErrorPageRegistrarBeanPostProcessor.class); } } private void registerSyntheticBeanIfMissing (BeanDefinitionRegistry registry, String name, Class<?> beanClass) {...} }
webServerFactoryCustomizerBeanPostProcessor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor , BeanFactoryAware { ...... public Object postProcessBeforeInitialization (Object bean, String beanName) throws BeansException { if (bean instanceof WebServerFactory) { this .postProcessBeforeInitialization((WebServerFactory)bean); } return bean; } public Object postProcessAfterInitialization (Object bean, String beanName) throws BeansException { return bean; } private void postProcessBeforeInitialization (WebServerFactory webServerFactory) { ((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this .getCustomizers(), webServerFactory, new Object[0 ]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> { customizer.customize(webServerFactory); }); }
步骤:
1)、SpringBoot根据导入的依赖情况,给容器中添加相应的AbstractServletWebServerFactory【比如TomcatServletWebServerFactory】
2)、容器中某个组件要创建对象就会惊动后置处理器;WebServerFactoryCustomizerBeanPostProcessor;
只要是嵌入式的Servlet容器工厂,后置处理器就工作;
3)、后置处理器,从容器中获取所有的WebServerFactoryCustomizer,调用定制器的定制方法
6、嵌入式Servlet容器启动原理;什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat;
获取嵌入式的Servlet容器工厂:
1)、SpringBoot应用启动运行run方法 2)、在SpringApplication
类中refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个组件】;如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext,否则:AnnotationConfigApplicationContext
3)、在ServletWebServerApplicationContext
中,refresh(context);刷新刚才创建好的ioc容器;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @Override public final void refresh () throws BeansException, IllegalStateException { try { super .refresh(); } catch (RuntimeException ex) { stopAndReleaseWebServer(); throw ex; } } 在父类的refresh方法中 try { postProcessBeanFactory(beanFactory); invokeBeanFactoryPostProcessors(beanFactory); registerBeanPostProcessors(beanFactory); initMessageSource(); initApplicationEventMulticaster(); onRefresh(); registerListeners(); finishBeanFactoryInitialization(beanFactory); finishRefresh(); }
4)、在父类的refresh()方法中调用了onRefresh()方法; 而web的ioc容器重写了onRefresh方法1 2 3 4 5 6 7 8 9 10 @Override protected void onRefresh () { super .onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server" , ex); } }
5)、webioc容器会创建嵌入式的Servlet容器;createWebServer ();1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private void createWebServer () { WebServer webServer = this .webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null ) { ServletWebServerFactory factory = getWebServerFactory(); this .webServer = factory.getWebServer(getSelfInitializer()); } else if (servletContext != null ) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context" , ex); } } initPropertySources(); }
6)、获取嵌入式的Servlet容器工厂:1 2 3 4 5 6 7 8 9 10 11 12 13 protected ServletWebServerFactory getWebServerFactory () { String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class); if (beanNames.length == 0 ) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing " + "ServletWebServerFactory bean." ); } if (beanNames.length > 1 ) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple " + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames)); } return getBeanFactory().getBean(beanNames[0 ], ServletWebServerFactory.class); }
从ioc容器中获取ServletWebServerFactory 组件;TomcatEmbeddedServletContainerFactory创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置;
7)、使用容器工厂获取嵌入式的Servlet容器:this.webServer = factory.getWebServer(getSelfInitializer());
8)、嵌入式的Servlet容器创建对象并启动Servlet容器;先启动嵌入式的Servlet容器,在根据refresh()中的流程将剩下的组件创建出来
IOC容器启动创建嵌入式的Servlet容器
7、使用外置的Servlet容器嵌入式Servlet容器:应用打成可执行的jar包
优点:简单、便携;
缺点:默认不支持JSP、优化定制比较复杂(使用定制器【ServerProperties、自定义EmbeddedServletContainerCustomizer】,自己编写嵌入式Servlet容器的创建工厂【EmbeddedServletContainerFactory】);
外置的Servlet容器:外面安装Tomcat—应用war包
的方式打包;
步骤
1)、必须创建一个war项目;(利用idea创建好目录结构)
2)、将嵌入式的Tomcat指定为provided;
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-tomcat</artifactId > <scope > provided</scope > </dependency >
3)、必须编写一个SpringBootServletInitializer的子类,并调用configure方法
1 2 3 4 5 6 7 8 9 public class ServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure (SpringApplicationBuilder application) { return application.sources(SpringBoot04WebJspApplication.class); } }
4)、启动服务器就可以使用;
原理
jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器;
war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer
】,启动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<Class<?>>;为这些WebApplicationInitializer类型的类创建实例;
4)、每一个WebApplicationInitializer都调用自己的onStartup;
5)、相当于我们的SpringBootServletInitializer
的类会被创建对象,并执行onStartup方法 (项目中的的ServletInitializer继承自SpringBootServletInitializer,而SpringBootServletInitializer实现了WebApplicationInitializer接口,相当于当项目启动时,将我们项目中的ServletInitializer类实例化了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @HandlesTypes({WebApplicationInitializer.class}) public class SpringServletContainerInitializer implements ServletContainerInitializer { public SpringServletContainerInitializer () { } public void onStartup (@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List<WebApplicationInitializer> initializers = new LinkedList(); Iterator var4; if (webAppInitializerClasses != null ) { var4 = webAppInitializerClasses.iterator(); while (var4.hasNext()) { Class<?> waiClass = (Class)var4.next(); if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try { initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0 ]).newInstance()); } catch (Throwable var7) { throw new ServletException("Failed to instantiate WebApplicationInitializer class" , var7); } } } } if (initializers.isEmpty()) { servletContext.log("No Spring WebApplicationInitializer types detected on classpath" ); } else { servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath" ); AnnotationAwareOrderComparator.sort(initializers); var4 = initializers.iterator(); while (var4.hasNext()) { WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next(); initializer.onStartup(servletContext); } } } }
6)、SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器
1 2 3 4 5 6 7 @Override public void onStartup (ServletContext servletContext) throws ServletException { this .logger = LogFactory.getLog(getClass()); WebApplicationContext rootAppContext = createRootApplicationContext(servletContext); 。。。。。。}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 protected WebApplicationContext createRootApplicationContext ( ServletContext servletContext) { 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); builder = configure(builder); 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" ); if (this .registerErrorPageFilter) { application.getSources().add(ErrorPageFilterConfiguration.class); } return run(application); }
7)、Spring的应用就启动并且创建IOC容器(SpringApplication
)(和前面web容器的创建一样)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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); 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应用
五、Docker详细请看
1.简介Docker是一个开源的应用容器引擎;是一个轻量级容器技术;
Docker支持将软件编译成一个镜像;然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像;
运行中的这个镜像称为容器,容器启动是非常快速的。
2.核心概念docker主机(Host) :安装了Docker程序的机器(Docker直接安装在操作系统之上);
docker客户端(Client) :连接docker主机进行操作;
docker仓库(Registry) :用来保存各种打包好的软件镜像;
docker镜像(Images) :软件打包好的镜像;放在docker仓库中;
docker容器(Container) :镜像启动后的实例称为一个容器;容器是独立运行的一个或一组应用
3.安装docker1.检查内核版本,必须是3.10及以上
2安装docker.
3.启动docker
1 2 systemctl start docker docker -v 查看版本,检查有没有成功安装
4、开机启动docker
3.docker常用命令&操作 1)、镜像操作操作 命令 说明 检索 docker search 关键字 eg:docker search redis 我们经常去docker hub上检索镜像的详细信息,如镜像的TAG。 拉取 docker pull 镜像名:tag :tag是可选的,tag表示标签,多为软件的版本,默认是latest (可到hub 去查看,eg:docker pull mysql:5.5) 列表 docker images 查看所有本地镜像 删除 docker rmi image-id 删除指定的本地镜像 (id通过docker images查看)
2)修改镜像源(一定要修改,不然慢死)修改 /etc/docker/daemon.json ,写入如下内容(如果文件不存在请新建该文件)
# 内容:
1 2 3 4 5 6 7 8 9 vim /etc/docker/daemon.json --------- { "registry-mirrors":["http://hub-mirror.c.163.com"] } ----------- sudo systemctl daemon-reload//重新加载 ----------- sudo systemctl restart docker//重启
(推荐:可以在阿里云官网搜索容器镜像服务 ,创建一个镜像服务,从镜像加速器 获取地址)
3)、容器操作步骤(以tomcat为例:):
1.下载tomcat镜像
如需选择具体版本,可以在https://hub.docker.com/搜索tomcat
1 docker pull tomcat:7.0.96-jdk8-adoptopenjdk-hotspot
2.根据镜像启动容器,不加TAG默认latest,如果没有下载latest会先去下载再启动
1 2 # 错误的创建,没有端口映射,正确的在下方 docker run --name mytomcat -d tomcat:latest
–name :给容器起个名字
-d :后台启动,不加就是前端启动,然后你就只能开一个新的窗口连接,不然就望着黑乎乎的窗口,啥也干不了,Ctrl+C即可退出,当然,容器也会关闭 完整命令:docker run -it -p 8888:8888 --name test (-v id:文件 文件) jiang/centostest (/bin/bash,dockerfile后面已====有该CMD,直接进入该容器)
docker run -p 8888:8888 --name test (-v id:文件 文件) -d jaing/centostest
(在后台运行,但如果前台没有一个进程会自动结束,因为-d命令会覆盖CMD)docker run -it --name dc02 --volumes-from dc01 tulong/centos
通过数据卷容器创建容器实现共享
3.查看运行中的容器
4.停止运行中的容器
1 2 3 4 5 docker stop 容器的id # 或者 docker stop 容器的名称,就是--name给起的哪个名字
5.查看所有的容器
6.启动容器
7.删除一个容器
8.启动一个做了端口映射的tomcat
1 2 3 docker run -d -p 8888:8080 tomcat # 或者 docker run -p 8888:8080--name tomcatt -d tomcat
-d:后台运行
-p: 将主机的端口映射到容器的一个端口 主机端口(8888):容器内部的端口(8080)
外界通过主机的8888端口就可以访问到tomcat,前提是8888端口开放
注意阿里云环境下需要配置(搞了我好久):
1 2 3 docker exec -it tomcat 容器id(通过docker ps查看) /bin/bash rmdir webapps mv webapps.dist webapps
因为所有的文件都在webapps.dist下,而访问的是webapps下的内容
9.关闭防火墙
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 查看防火墙状态 service firewalld status # 关闭防火墙 service firewalld stop # 设置防火墙对该端口不设拦截(推荐) iptables -I INPUT -p tcp --dport 8889 -j ACCEPT # 或者 firewall-cmd --zone=public --add-port=22/tcp --permanent firewall-cmd --reload #重新加载 firewall-cmd --zone=public --query-port=22/tcp #查询是否生效 firewall-cmd --zone=public --remove-port=22/tcp --permanent #限制端口 firewall-cmd --zone=public --list-ports #查看端口
阿里云环境下还要增加一个安全组
10.查看容器的日志
1 2 docker logs 容器id/名字 docker logs -ft --tail 倒数几条 容器ID
11.可以用一个镜像启动多个容器
1 docker run -d -p 8889:8080 tomcat
12. 查看容器的内部细节
13. 进入正在运行的容器并以命令行交互:
1 2 3 4 5 6 7 8 直接进入容器启动命令的终端,不会启动新的进程 docker attach 容器ID 是在容器中打开新的终端,并且可以启动新的进程 docker exec -it 容器ID /bin/bash
14. 退出容器
1 2 3 exit 容器停止并退出 Ctrl + P + Q 容器不停止退出
15.从容器内拷贝文件到主机上(主机到容器类似):
1 docker cp 容器ID:容器内路径 目标主机路径
16.镜像Commit
1 docker commit -m="提交的描述信息" -a="作者" 容器ID 要创建的目标镜像名:[标签名]
17.build镜像
1 docker build -f /mydocker/dockerfile(文件路径) -t tulong/centos (镜像名) . (注意最后面有一个点)
18.数据卷容器
1 docker run -it --name dc02 --volumes-from dc01 tulong/centos
配置文件
命名的容器挂载数据卷,其它容器通过挂载这个(父容器)实现数据共享,挂载数据卷的容器,称之为数据卷容器. 容器之间配置信息的传递,数据卷的生命周期一直持续到没有容器使用它为止
更多命令参看https://docs.docker.com/engine/reference/commandline/docker/ 可以参考每一个镜像的文档
### 4.安装myqsl(5.5版本,5.7版本有问题) ##### 获取镜像
##### 创建容器
1 docker run -p 3306:3306 --name mysqldb -e MYSQL_ROOT_PASSWORD=123456-d mysql:5.5
##### 出现错误,删除该容器,并`重启服务`
1 systemctl restart docker
##### 配置防火墙
1 2 iptables -I INPUT -p tcp --dport 8889 -j ACCEPT
##### 配置阿里云安全组
其他的几个高级操作1 2 3 4 5 6 docker run --name mysql03 -v /conf/mysql:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag 把主机的/conf/mysql文件夹挂载到 mysqldocker容器的/etc/mysql/conf.d文件夹里面 改mysql的配置文件就只需要把mysql配置文件放在自定义的文件夹下(/conf/mysql) docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci 指定mysql的一些配置参数
5.安装Redis1 docker run -p 6379:6379 --name redis -v /root/redis/data:/data -v /root/redis/conf/redis.conf:/usr/local/etc/redis/redis.conf -d redis redis-server /usr/local/etc/redis/redis.conf --appendonly yes --requirepass "jcl5412415845"
6.安装nginx记得挂载conf.d和nginx.conf,其中conf.d里有一个default.conf,并且还需要挂载一个存放静态资源的文件夹,不然很麻烦。
1 [root@iZ2ze71edtq2kxflzq2m01Z conf.d]# docker run --name nginxtest -d -p 80:80 -v /root/nginx/conf.d:/etc/nginx/conf.d -v /root/nginx/nginx.conf:/etc/nginx/nginx.conf -v /root/nginx/html:/usr/share/nginx/html nginx
六、SpringBoot与数据访问 1、JDBC 1). 测试1 2 3 4 5 6 7 8 9 <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 >
1 2 3 4 5 6 spring: datasource: username: xxx password: xxx url: jdbc:mysql://localhost:3306/learn?characterEncoding=UTF-8&serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver
springboot默认是使用com.zaxxer.hikari.HikariDataSource作为数据源,2.0以下是用org.apache.tomcat.jdbc.pool.DataSource作为数据源;
数据源的相关配置都在DataSourceProperties
里面;
2). 自动配置原理:dbc的相关配置都在org.springframework.boot.autoconfigure.jdbc
包下
参考DataSourceConfiguration
,根据配置创建数据源,默认使用Hikari连接池;可以使用spring.datasource.type
指定自定义的数据源类型;
springboot默认支持的连池:
org.apache.commons.dbcp2.BasicDataSource com.zaxxer.hikari.HikariDataSource org.apache.tomcat.jdbc.pool.DataSource 自定义数据源类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(DataSource.class) @ConditionalOnProperty(name = "spring.datasource.type") static class Generic { @Bean public DataSource dataSource (DataSourceProperties properties) { return properties.initializeDataSourceBuilder().build(); } }
3). 启动引用执行sqlSpringBoot在创建连接池后还会运行预定义的SQL脚本文件,具体参考org.springframework.boot.autoconfigure.jdbc.DataSourceInitializationConfiguration
配置类,
在该类中注册了dataSourceInitializerPostProcessor
(老版本使用的是ApplicationListener)
下面是获取schema脚本文件的方法
1 List<Resource> scripts = this .getScripts("spring.datasource.schema" , this .properties.getSchema(), "schema" );
可以看出,如果我们没有在配置文件中配置脚本的具体位置,就会在classpath下找schema-all.sql
,schema.sql
,platform获取的是all,platform可以在配置文件中修改
具体查看createSchema()方法和initSchema()方法
initSchema()方法获取的是data-all.sql
,data.sql
我们也可以在配置文件中配置sql文件的位置(getScripts方法中的resources就是配置文件DataSourcePeoperties中的一个list集合,为data和schema)
1 2 3 4 5 spring: datasource: schema: - classpath:department.sql - 指定位置
测试:
在类路径下创建schema.sql,运行程序查看数据库是否存在该表
1 2 3 4 5 6 DROP TABLE IF EXISTS `department`;CREATE TABLE `department` ( `id` int (11 ) NOT NULL AUTO_INCREMENT, `departmentName` varchar (255 ) DEFAULT NULL , PRIMARY KEY (`id`) ) ENGINE= InnoDB AUTO_INCREMENT= 1 DEFAULT CHARSET= utf8;
程序启动后发现表并没有被创建,DEBUG查看以下,发现在运行之前会有一个判断
1 2 3 4 void initSchema () { List<Resource> scripts = getScripts("spring.datasource.data" , this .properties.getData(), "data" ); ...... }
上面方法也不知道在干什么,反正就是只要是NEVER和EMBEDDED就为true,而DataSourceInitializationMode枚举类中除了这两个就剩下ALWAYS了,可以在配置文件中配置为ALWAYS
1 2 3 4 5 6 7 spring: datasource: username: root password: root url: jdbc:mysql://172.16.145.137:3306/springboot driver-class-name: com.mysql.cj.jdbc.Driver initialization-mode: always
schema.sql,schema-all.sql
:建表语句
data.sql,data-all.sql
:插入数据
当然混合使用也可以,愿意咋来咋来
注意:项目每次启动都会执行一次sql
4)、操作数据库:自动配置了JdbcTemplate操作数据库 2、整合Druid数据源选择哪个数据库连接池
DBCP2 是 Appache 基金会下的项目,是最早出现的数据库连接池 DBCP 的第二个版本。 C3P0 最早出现时是作为 Hibernate 框架的默认数据库连接池而进入市场。 Druid 是阿里巴巴公司开源的一款数据库连接池,其特点在于有丰富的附加功能。 HikariCP 相较而言比较新,它最近两年才出现,据称是速度最快的数据库连接池。最近更是被 Spring 设置为默认数据库连接池。 不选择 C3P0 的原因:
C3P0 的 Connection 是异步释放。这个特性会导致释放的在某些情况下 Connection 实际上 still in use ,并未真正释放掉,从而导致连接池中的 Connection 耗完,等待状况。 Hibernate 现在对所有数据库连接池一视同仁,官方不再指定『默认』数据库连接池。因此 C3P0 就失去了『官方』光环。 不选择 DBCP2 的原因:
相较于 Druid 和 HikariCP,DBCP2 没有什么特色功能/卖点。基本上属于 能用,没毛病 的情况,地位显得略有尴尬。 在 Spring Boot 项目中加入druid-spring-boot-starter依赖 (点击查询最新版本 )
1 2 3 4 5 <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > <version > 1.1.20</version > </dependency >
配置信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 spring: datasource: username: root password: root url: jdbc:mysql://192.168.1.2:3306/learn?characterEncoding=UTF-8&serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver initialization-mode: always type: com.alibaba.druid.pool.DruidDataSource initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 30000 validationQuery: select 'x' ; testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize: 20 filters: stat,wall,slf4j connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 useGlobalDataSourceStat: true web-stat-filter: enabled: true url-pattern: /* exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" stat-view-servlet: enabled: true url-pattern: /druid/* login-username: admin login-password: root allow: 127.0 .0 .1
类中配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @Configuration public class DruidConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource durid () { return new DruidDataSource(); } @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.22" ); bean.setInitParameters(initParams); return bean; } @Bean public FilterRegistrationBean webStatFilter () { FilterRegistrationBean bean=new FilterRegistrationBean(new WebStatFilter()); bean.setUrlPatterns(Arrays.asList("/*" )); Map<String,String> initParams=new HashMap<>(); initParams.put("exclusions" ,"*.js,*.css,/druid/*" ); bean.setInitParameters(initParams); return bean; } }
3、整合MyBatis 1). 引入依赖1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <modelVersion > 4.0.0</modelVersion > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.2.5.RELEASE</version > <relativePath /> </parent > <groupId > com.example</groupId > <artifactId > demo</artifactId > <version > 0.0.1-SNAPSHOT</version > <name > demo</name > <description > Demo project for Spring Boot</description > <properties > <java.version > 1.8</java.version > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jdbc</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 2.1.2</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-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > <version > 1.1.21</version > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
依赖关系图
步骤:
1)、配置数据源相关属性(见上一节Druid)
2)、给数据库建表
3)、创建JavaBean
2).注解版 开启驼峰命名法我们的实体类和表中的列名一致,一点问题也没有
我们把department表的departmentName列名改为department_name看看会发生什么
访问:http://localhost:8080/dep/1获取数据
1 [{"id" :1 ,"departmentName" :null }]
由于列表和属性名不一致,所以就没有封装进去,我们表中的列名和实体类属性名都是遵循驼峰命名规则的,可以开启mybatis的开启驼峰命名配置
1 2 3 mybatis: configuration: map-underscore-to-camel-case: true
然后重启项目,重新插入数据,再查询就发现可以封装进去了
也可以通过向spring容器中注入org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer的方法设置mybatis参数
1 2 3 4 5 6 7 8 9 10 11 12 13 @Configuration public class MybatisConfig { @Bean public ConfigurationCustomizer mybatisConfigurationCustomizer () { return new ConfigurationCustomizer() { @Override public void customize (org.apache.ibatis.session.Configuration configuration) { configuration.setMapUnderscoreToCamelCase(true ); } }; } }
Mapper扫描使用@mapper注解的类可以被扫描到容器中,但是每个Mapper都要加上这个注解就是一个繁琐的工作,能不能直接扫描某个包下的所有Mapper接口呢,当然可以,在springboot启动类上加上@MapperScan
1 2 3 4 5 6 7 8 9 @MapperScan("cn.clboy.springbootmybatis.mapper") @SpringBootApplication public class SpringbootMybatisApplication { public static void main (String[] args) { SpringApplication.run(SpringbootMybatisApplication.class, args); } }
3).配置文件版创建mybatis全局配置文件(建议别写,我的好像跟yml文件中有些配置冲突了,不知道的为什么,直接在yml配置一些就可以了)
1 2 3 4 5 6 7 8 <?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 > <typeAliases > <package name ="cn.clboy.springbootmybatis.model" /> </typeAliases > </configuration >
1 2 3 mybatis: config-location: classpath:mybatis/mybatis-config.xml 指定全局配置文件的位置 mapper-locations: classpath:mybatis/mapper/*.xml 指定sql映射文件的位置
配置文件(application.yaml)中指定配置文件和映射文件的位置
1 2 3 mybatis: config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml
4).完整使用创建一个接口
1 2 3 4 5 6 @Mapper public interface EmployeeMapper { public Employee getEmpById (@Param("id") int id) ; public void insertEmp (Employee emp) ; }
创建一个mapper的xml文件(直接在resources下创建一个mybatis包进行创建)
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN" "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd" > <mapper namespace ="com.example.demo.mapper.EmployeeMapper" > <select id ="getEmpById" resultType ="Employee" parameterType ="int" > select * from employee where id=#{id} </select > <insert id ="insertEmp" parameterType ="Employee" useGeneratedKeys ="true" keyProperty ="id" > insert into employee values(#{id},#{lastName},#{eami},#{gender},#{d_id}) </insert > </mapper >
yml文件进行配置mybatis的configuration(或者用ConfigurationCustomizer进行配置)
1 2 3 4 5 6 mybatis: mapper-locations: classpath:mybatis/mapper/*.xml configuration: map-underscore-to-camel-case: true type-aliases-package: com.example.demo.pojo
附: 使用mybatis的xml文件进行配置
1 2 3 4 5 6 7 8 9 10 11 12 <?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 > <typeAliases > <package name ="com.example.demo.pojo" /> </typeAliases > </configuration >
千万别写上上面yml中的其他配置,可能会报错,并且我前面在yml或者ConfiguratinoCustomizer中加入的驼峰命名法配置也失效了,要在xml文件中配置才生效
1 2 3 mybatis: config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml
更多使用参照
http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
4、整合SpringData JPA 1)、SpringData简介
2)、整合SpringData JPAJPA:ORM(Object Relational Mapping);
1)、编写一个实体类(bean)和数据表进行映射,并且配置好映射关系;
1 2 3 4 5 6 7 8 9 10 11 12 13 @Entity @Table(name = "tbl_user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(name = "last_name",length = 50) private String lastName; @Column private String email;
2)、编写一个Dao接口来操作实体类对应的数据表(Repository)
1 2 3 4 public interface UserRepository extends JpaRepository <User ,Integer > {}
3)、基本的配置JpaProperties
1 2 3 4 5 6 7 spring: jpa: hibernate: ddl-auto: update show-sql: true
七、启动配置原理几个重要的事件回调机制
配置在META-INF/spring.factories
ApplicationContextInitializer
SpringApplicationRunListener
只需要放在ioc容器中
ApplicationRunner
CommandLineRunner
启动流程:
1、创建SpringApplication对象1 2 3 4 public static void main (String[] args) { SpringApplication.run(xxx.class, args); }
从run方法开始,创建SpringApplication,然后再调用run方法
1 2 3 4 5 6 7 8 public static ConfigurableApplicationContext run (Class<?> primarySource, String... args) { return run(new Class<?>[] { primarySource }, args); } public static ConfigurableApplicationContext run (Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
1 2 3 public SpringApplication (Class<?>... primarySources) { this (null , primarySources); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public SpringApplication (ResourceLoader resourceLoader, Class<?>... primarySources) { this .resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null" ); this .primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this .webApplicationType = WebApplicationType.deduceFromClasspath(); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this .mainApplicationClass = deduceMainApplicationClass(); }
SpringApplication.java
1 2 3 4 5 6 7 8 private <T> Collection<T> getSpringFactoriesInstances (Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
SpringFactoriesLoader.java
1 2 3 4 5 6 7 8 9 10 11 public static List<String> loadFactoryNames (Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { ······ Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories" ) : ClassLoader.getSystemResources("META-INF/spring.factories" ); ······ }
2、运行run方法1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); //声明IOC容器 ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList(); this.configureHeadlessProperty(); //从类路径下META‐INF/spring.factories获取SpringApplicationRunListeners,原理同上方获取ApplicationContextInitializer和ApplicationListener SpringApplicationRunListeners listeners = this.getRunListeners(args); //遍历上一步获取的所有SpringApplicationRunListener,调用其starting方法 listeners.starting(); try { //封装命令行 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //准备环境,把上面获取到的listeners传过去,见2.1 ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); this.configureIgnoreBeanInfo(environment); //打印Banner,就是控制台那个Spring字符画 Banner printedBanner = this.printBanner(environment); //根据当前环境利用反射创建IOC容器 context = this.createApplicationContext(); //从类路径下META‐INF/spring.factories获取SpringBootExceptionReporter,原理同2中获取ApplicationContextInitializer和ApplicationListener exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context); //准备IOC容器,见2.2 this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); //刷新IOC容器,可查看配置嵌入式Servlet容器原理 this.refreshContext(context); //这是一个空方法 this.afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); } //调用所有SpringApplicationRunListener的started方法 listeners.started(context); //见2.4 ,从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调,ApplicationRunner先回调,CommandLineRunner再回调 this.callRunners(context, applicationArguments); } catch (Throwable var10) { this.handleRunFailure(context, var10, exceptionReporters, listeners); throw new IllegalStateException(var10); } try { //调用所有SpringApplicationRunListener的running方法 listeners.running(context); return context; } catch (Throwable var9) { this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null); throw new IllegalStateException(var9); } }
2.1
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
//获取或者创建环境,有则获取,无则创建
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
//配置环境
this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach((Environment)environment);
//创建环境完成后,调用前面获取的所有SpringApplicationRunListener的environmentPrepared方法
listeners.environmentPrepared((ConfigurableEnvironment)environment);
this.bindToSpringApplication((ConfigurableEnvironment)environment);
if (!this.isCustomEnvironment) {
environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
}
ConfigurationPropertySources.attach((Environment)environment);
return (ConfigurableEnvironment)environment;
}
2.2
protected ConfigurableApplicationContext createApplicationContext() {
Class> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch(this.webApplicationType) {
case SERVLET:
contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
break;
case REACTIVE:
contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
break;
default:
contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
}
} catch (ClassNotFoundException var3) {
throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
}
}
return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}
2.3
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//将创建好的环境放到IOC容器中
context.setEnvironment(environment);
//注册一些组件
this.postProcessApplicationContext(context);
//获取所有的ApplicationContextInitializer调用其initialize方法,这些ApplicationContextInitializer就是在2步骤中获取的,见2.3.1
this.applyInitializers(context);
//回调所有的SpringApplicationRunListener的contextPrepared方法,这些SpringApplicationRunListeners是在步骤3中获取的
listeners.contextPrepared(context);
//打印日志
if (this.logStartupInfo) {
this.logStartupInfo(context.getParent() == null);
this.logStartupProfileInfo(context);
}
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
Set<Object> sources = this.getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
this.load(context, sources.toArray(new Object[0]));
//回调所有的SpringApplicationRunListener的contextLoaded方法
listeners.contextLoaded(context);
}
2.3.1
protected void applyInitializers(ConfigurableApplicationContext context) {
Iterator var2 = this.getInitializers().iterator();
while(var2.hasNext()) {
ApplicationContextInitializer initializer = (ApplicationContextInitializer)var2.next();
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
2.4
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List runners = new ArrayList();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
Iterator var4 = (new LinkedHashSet(runners)).iterator()
while(var4.hasNext()) {
Object runner = var4.next();
if (runner instanceof ApplicationRunner) {
this.callRunner((ApplicationRunner)runner, args);
}
if (runner instanceof CommandLineRunner) {
this.callRunner((CommandLineRunner)runner, args);
}
}
}
3、事件监听机制 1).创建ApplicationContextInitializer和SpringApplicationRunListener的实现类,并在META-INF/spring.factories文件中配置
1 2 3 4 5 6 public class HelloApplicationContextInitializer implements ApplicationContextInitializer <ConfigurableApplicationContext > { @Override public void initialize (ConfigurableApplicationContext applicationContext) { System.out.println("ApplicationContextInitializer...inittialize..." +applicationContext); } }
HelloSpringApplicationRunListener,记得配置一个带两个参数的构造器,可以去SpringApplicationRunListener的其他实现类中查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class HelloSpringApplicationRunListener implements SpringApplicationRunListener { public HelloSpringApplicationRunListener (SpringApplication application,String[] args) { } @Override public void starting () { System.out.println("SpringApplicationRunListener..." ); } @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("TestSpringApplicationRunListener.contextLoaded" ); } @Override public void started (ConfigurableApplicationContext context) { System.out.println("TestSpringApplicationRunListener.started" ); } @Override public void running (ConfigurableApplicationContext context) { System.out.println("TestSpringApplicationRunListener.running" ); } @Override public void failed (ConfigurableApplicationContext context, Throwable exception) { System.out.println("TestSpringApplicationRunListener.failed" ); }
因为这两个类是通过加载spring.factories获得的,所以需要创建在spring.facories中配置
1 2 3 4 5 org.springframework.context.ApplicationContextInitializer=\ com.example.demo.listener.HelloApplicationContextInitializer org.springframework.boot.SpringApplicationRunListener=\ com.example.demo.listener.HelloSpringApplicationRunListener
启动报错:说是没有找到带org.springframework.boot.SpringApplication和String数组类型参数的构造器,需要添加一个构造器,看上方代码
2).创建ApplicationRunner实现类和CommandLineRunner实现类,注入到容器中
ApplicationRunner
1 2 3 4 5 6 7 @Component public class HelloApplicationRunner implements ApplicationRunner { @Override public void run (ApplicationArguments args) throws Exception { System.out.println("ApplicationRunner...run\t--->" + Arrays.asList(args)); } }
CommandLineRunner
1 2 3 4 5 6 7 @Component public class HelloCommandLineRunner implements CommandLineRunner { @Override public void run (String... args) throws Exception { System.out.println("TestCommandLineRunn.runt\t--->" + Arrays.toString(args)); } }
4.修改Banner1 2 3 4 5 6 7 static final String BANNER_LOCATION_PROPERTY = "spring.banner.location" ; static final String BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location" ; static final String DEFAULT_BANNER_LOCATION = "banner.txt" ; static final String[] IMAGE_EXTENSION = { "gif" , "jpg" , "png" };
默认是找类路径下的banner.txt,可以在配置文件中修改
1 spring.banner.location =xxx.txt
生成banner的网站:http://patorjk.com/software/taag
也可以使用图片(将其像素解析转换成assii编码之后打印),默认是在类路径下找名为banner
后缀为"gif
", "jpg"
, "png
"的图片
1 static final String[] IMAGE_EXTENSION = new String[]{"gif" , "jpg" , "png" };
也可以在配置文件中指定
1 spring.banner.image.location =classpath:abc.png
八.自定义Starter启动器只用来做依赖导入
专门来写一个自动配置模块;
启动器依赖自动配置模块,项目中引入相应的starter就会引入启动器的所有传递依赖
启动器 启动器模块是一个空 JAR 文件,仅提供辅助性依赖管理,这些依赖可能用于自动 装配或者其他类库
1.命名规约spring-boot-starter-模块名
eg:spring-boot-starter-web、spring-boot-starter-jdbc、spring-boot-starter-thymeleaf
模块名-spring-boot-starter
eg:mybatis-spring-boot-start
2.如何编写自动配置1 2 3 4 5 6 7 @Configuration @ConditionalOnXXX @AutoConfigureAfter @Bean @ConfigurationPropertie 结合相关xxxProperties类来绑定相关的配置@EnableConfigurationProperties public class XxxxAutoConfiguration {
自动配置类要能加载,需要将启动就加载的自动配置类配置在META-INF/spring.factories 中
eg:
1 2 3 4 # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\ org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
3.案例1.创建一个自动配置模块,和创建普通springboot项目一样,不需要引入其他starter
2.删除掉多余的文件和依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>atguigu-spring-boot-starter-autoconfigurer</artifactId> <version>0.0.1-SNAPSHOT</version> <name>atguigu-spring-boot-starter-autoconfigurer</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--引入spring‐boot‐starter;所有starter的基本配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!--可以生成配置类提示文件--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> </dependencies> </project>
删掉了启动类
3.创建配置类和自动配置类
1 2 3 4 5 6 7 ConfigurationProperties(prefix = "atguigu" ) public class HelloProperties { private String prefix; private String suffix; ......省略set,get方法和构造器 }
1 2 3 4 5 6 7 8 9 10 11 12 13 public class HelloService { HelloProperties helloProperties; public String sayHelloAtguigu (String name) { return helloProperties.getPrefix()+name+helloProperties.getSuffix(); } .....省略set,get方法 }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Configuration @ConditionalOnWebApplication @EnableConfigurationProperties(HelloProperties.class) public class HelloServiceAutoConfiguration { HelloProperties helloProperties; public HelloServiceAutoConfiguration (HelloProperties helloProperties) { this .helloProperties = helloProperties; } @Bean public HelloService helloService () { HelloService service=new HelloService(); service.setHelloProperties(helloProperties); return service; } }
4.在resources文件夹下创建META-INF/spring.factories
1 2 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.starter.HelloServiceAutoConfiguration
5.安装到本地仓库
6.创建starter,选择maven工程即可,只是用于管理依赖,添加对AutoConfiguration模块的依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.atguigustarter</groupId > <artifactId > atguigu-spring-boot-starter</artifactId > <version > 0.0.1-SNAPSHOT</version > <properties > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <dependency > <groupId > com.example</groupId > <artifactId > atguigu-spring-boot-starter-autoconfigurer</artifactId > <version > 0.0.1-SNAPSHOT</version > </dependency > </dependencies > </project >
7.安装到本地仓库
8.创建项目测试,选择添加web场景,因为设置是web场景才生效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.2.5.RELEASE</version > <relativePath /> </parent > <groupId > com.atguigu</groupId > <artifactId > spring-boot-08-starter-test</artifactId > <version > 0.0.1-SNAPSHOT</version > <name > spring-boot-08-starter-test</name > <description > Demo project for Spring Boot</description > <properties > <java.version > 1.8</java.version > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > com.atguigustarter</groupId > <artifactId > atguigu-spring-boot-starter</artifactId > <version > 0.0.1-SNAPSHOT</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
9.创建Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 @RestController public class HelloController { @Autowired HelloService helloService; @GetMapping("hello") public String hello () { String str=helloService.sayHelloAtguigu("haha" ); return str; } }
10.在配置文件中配置
1 2 atguigu.prefix =ATGUIGU atguigu.suffix =DADAAD
九.缓存 1.JSR107Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。
CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。
CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。
Entry是一个存储在Cache中的key-value对。
Expiry 每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置
2.基础环境搭建 1).引入cache模块和mybatis
1 2 3 4 5 6 7 8 9 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-cache</artifactId > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 2.1.2</version > </dependency >
2) 导入数据库文件,创建出表 3)创建javaBean封装数据 4)整合MyBatis操作数据库配置数据源信息
1 2 3 4 5 spring.datasource.url=jdbc:mysql://localhost:3306/springboot?characterEncoding=UTF-8&serverTimezone=UTC spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver mybatis.configuration.map-underscore-to-camel-case=true
创建mapper,service和controller
3. 体验缓存 1) EnableCaching(开启基于注解的缓存)1 2 3 4 @MapperScan("com.atguigu.cache.mapper") @EnableCaching @SpringBootApplication public class SpringBoot01CacheApplication {
2)Cacheable(开启缓存)1 @Cacheable(cacheNames = "emp",key = "#id")
4.相关概念Cache 缓存接口,定义缓存操作。实现有: RedisCache 、 EhCacheCache 、 ConcurrentMapCache 等 CacheManager **缓存管理器,管理各种缓存( ** Cache )组件 @Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 @CacheEvict 清空缓存 @CachePut 保证方法被调用,又希望结果被缓存。 @EnableCaching 开启基于注解的缓存 keyGenerator 缓存数据时 key 生成策略 serialize **缓存数据时 ** value 序列化策略
5. @Cacheable的参数
cacheNames/value 指定缓存的名字;将方法的返回结果放在哪个缓存(Cache)中,是数组的方式,可以指定多个。
key 缓存数据使用的key;可以用它来指定。默认是使用方法参数的值
1- 方法的返回值。也可以用SpEL编写
key可以使用的一些值
methodName root object 当前被调用的方法名 #root.methodName method root object 当前被调用的方法 #root.method.name target root object 当前被调用的目标对象 #root.target targetClass root object 当前被调用的目标对象类 #root.targetClass args root object 当前被调用的方法的参数列表 #root.args[0] caches root object 当前方法调用使用的缓存列表(如@Cacheable(value={“cache1”, “cache2”})),则有两个cache #root.caches[0].name argument name evaluation context 方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的形式,0代表参数的索引; #iban 、 #a0 、 #p0 result evaluation context 方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,’cache put’的表达式 ’cache evict’的表达式beforeInvocation=false) #result
1 2 3 @Cacheable(cacheNames = "emp",key = "#id") @Cacheable(cacheNames="emp",key="#root.args[0]") @Cacheable(cacheNames = "emp",key="#root.methodName+'['+#id+']'")
keyGenerator key的生成器,可以自己指定key的生成器的组件id
注意:key和keyGenerator二选一,不然会报错
1 2 3 4 5 6 7 8 9 10 11 12 13 @Configuration public class MyKeyGenerator { @Bean public KeyGenerator keyGenerator () { return new KeyGenerator() { @Override public Object generate (Object target, Method method, Object... params) { return method.getName()+"[" + Arrays.asList(params).toString()+"]" ; } }; } }
1 2 3 4 5 6 7 8 9 10 11 @GetMapping("/emp/{id}") public Employee getEmployee (@PathVariable("id") Integer id) { return employeeService.getEmp(id,"testGenerator" , Arrays.asList("1" ,"2" ,"3" )); } @Cacheable(cacheNames = "emp",condition = "#id>0",keyGenerator = "keyGenerator") public Employee getEmp (Integer id, String name, List<String> list) { System.out.println("查询" +id+"员工" ); Employee employee=employeeMapper.getEmpById(id); return employee; }
产生的key为:getEmp[[1, testGenerator, [1, 2, 3]]]
condition 指定符合条件的情况下才缓存(在方法调用前或调用后都能判断 )
1 @Cacheable(cacheNames = "emp",key = "#id",condition = "#id>0 and #root.methodName eq 'aaa'")
unless 否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存
1 2 @Cacheable(cacheNames = "emp",key = "#id",condition = "#id>1",unless = "#id==2")
结果:当id=1或者id=2时不缓存
CacheManager 管理多个Cache组件的,对缓存的真正CRUD操作在Cache中,每一个缓存组件有自己唯一一个名字。
cacheResolver 指定获取解析器.
注意:cacheManager和cacheResolver二选一
sync 是否使用异步属性,当使用了异步模式,unless就不支持了
6. @CachePut @CachePut:既调用方法,又更新缓存数据。修改了数据库的某个数据,同时更新缓存
调用时机(和Cacheable相反):
1.先调用了目标方法
2.将目标方法的结果放入缓存中
测试步骤
1.查询一号员工;查到的结果会放到缓存中
key:Id:1 ------value:lastName:张三
2.以后查询还是之前的结果
3.更新一号员工[lastName:zhangsan]
将方法的返回值也放进缓存了;
key:传入的employee对象 -------value:返回的employee对象
4.查询一号员工
应该是更新后的员工(如果没有指定key,那么查询的结果没变。因为查询的key和更新产生数据的key不一样
)
key="#employee.id ":使用传入的参数的员工id
key=“#result.id ”:使用返回的id
1 2 3 4 5 6 @CachePut(cacheNames = "emp",key = "#employee.id") public Employee updateEmp (Employee employee) { System.out.println("updateEmp" +employee); employeeMapper.updateEmp(employee); return employee; }
注意:如果还是不一样,请检查更新的key和查询的key是否一样。还有可能一个用的是key,一个是keyGeneratoer
7. @CacheEvict key="#id":清除指定key的缓存
allEntries=false/true:是否清除全部key的缓存
beforeInvocation=false/true:是否在方法调用前删除,默认是在方法执行之后清除。
当为false(默认)时,如果出现异常,缓存则不会被清除
当为true时,不敢有没有出现异常,都会被清除
1 2 3 4 @CacheEvict(cacheNames = "emp",/*key = "#id"*/allEntries = true,beforeInvocation=true) public void deleteEmp (Integer id) { System.out.println("deleteEmp:" +id); }
8.@Caching 用于构建复杂的缓存条件(cacheable,put,evict)
1 2 3 4 5 6 @Caching( cacheable = {@Cacheable(value = "emp",key = "#lastName")}, put = {@CachePut(value = "emp",key = "#result.id"), @CachePut(value = "emp",key = "#result.email")} //evict={} )
9.@CacheConfig 用于配置公共的参数(cacheNames,keyGenerator,cacheManager,cacheResolver)
10.缓存工作原理 CacheAutoConfiguration1 2 3 4 5 6 7 8 9 @ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver") @EnableConfigurationProperties(CacheProperties.class) @AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class, HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class }) @Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class }) public class CacheAutoConfiguration {
CacheConfigurationImportSelector1 2 3 4 5 6 7 8 9 10 11 12 13 static class CacheConfigurationImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { CacheType[] types = CacheType.values(); String[] imports = new String[types.length]; for (int i = 0 ; i < types.length; i++) { imports[i] = CacheConfigurations.getConfigurationClass(types[i]); } return imports; } }
导入的配置类:
哪个配置默认生效(通过debug=true查看)?
SimpleCacheConfiguration给容器中注册一个一个CacheManager:ConcurrentMapCacheManager
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(CacheManager.class) @Conditional(CacheCondition.class) class SimpleCacheConfiguration { @Bean ConcurrentMapCacheManager cacheManager (CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers) { ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager(); List<String> cacheNames = cacheProperties.getCacheNames(); if (!cacheNames.isEmpty()) { cacheManager.setCacheNames(cacheNames); } return cacheManagerCustomizers.customize(cacheManager); } }
ConcurrentMapCacheManager可以获取ConcurrentMapCache类型的缓存组件,并且保存到cacheMap中;(CacheNames-ConcurrentMapCache )
1 2 3 4 5 6 7 8 9 10 11 12 public class ConcurrentMapCacheManager implements CacheManager , BeanClassLoaderAware { private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16 ); ... protected Cache createConcurrentMapCache (String name) { SerializationDelegate actualSerialization = (isStoreByValue() ? this .serialization : null ); return new ConcurrentMapCache(name, new ConcurrentHashMap<>(256 ), isAllowNullValues(), actualSerialization); } }
ConcurrentMapCacheConcurretnMapCache的作用是将数据保存在ConcurrentMap当中(key-value )
1 2 3 4 5 public class ConcurrentMapCache extends AbstractValueAdaptingCache { private final String name; private final ConcurrentMap<Object, Object> store;
运行流程方法运行之前,先去ConcurrentMapCacheManager中查询Cache(缓存组件),按照cacheNames指定的名字来获取。第一次获取缓存如果没有Cache组件会自动创建出来放在cacheMap中。
在Cache中查找缓存的内容,使用一个key,默认就是方法的参数
哪么这个key是怎么生成的呢?
在CacheAspectSupport
中,通过keyGenerator中获取。
1 2 3 4 5 6 7 8 9 @Nullable protected Object generateKey (@Nullable Object result) { if (StringUtils.hasText(this .metadata.operation.getKey())) { EvaluationContext evaluationContext = createEvaluationContext(result); return evaluator.key(this .metadata.operation.getKey(), this .metadata.methodKey, evaluationContext); } return this .metadata.keyGenerator.generate(this .target, this .metadata.method, this .args); }
keyGenerator(SimpleKeyGenerator
)是通过CacheOperationMetadata
中获取
1 2 3 4 5 protected static class CacheOperationMetadata { ... private final KeyGenerator keyGenerator; ... }
1 2 3 4 5 6 7 8 9 10 11 12 13 public static Object generateKey (Object... params) { if (params.length == 0 ) { return SimpleKey.EMPTY; } if (params.length == 1 ) { Object param = params[0 ]; if (param != null && !param.getClass().isArray()) { return param; } } return new SimpleKey(params); }
如果没有参数,会自己new一个SimpleKey对象 如果就一个参数,key=参数的值 如果有多个参数,key=new SimpleKey(params); 没有查到缓存就调用目标方法 将目标方法放到map当中(ConcurrentMap) ,以后再要调用,就可以直接使用缓存中的数据
核心
使用CacheManager(ConcurrentMapCacheManager)按照名字得到Cache(ConcurrentMapCache)组件 key使用keyGenerator生成的,默认是SimpleKeyGenerator 11.搭建redis环境1).docker运行redis
1 docker run -p 6379:6379 --name redis -v /root/redis/data:/data -v /root/redis/conf/redis.conf:/usr/local/etc/redis/redis.conf -d redis redis-server /usr/local/etc/redis/redis.conf --appendonly yes --requirepass "xxx"
2).安装软件(不需要)
3)导入依赖
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency >
4)修改配置文件,连接redis
1 2 spring.redis.host=192.168.10.1 spring.redis.password=xxx
12.RedisAutoConfiguration创建了两个用于操作redis数据库的组件:RedisTemplate
和StringRedisTemplate(用于操作字符串的)
(类似于操作sql的JdbcTemplate)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<Object, Object> redisTemplate (RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate (RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }
13.测试1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Autowired RedisTemplate redisTemplate; @Autowired StringRedisTemplate stringRedisTemplate; @Autowired RedisTemplate empRedisTemplate; @Test void testRedis () { stringRedisTemplate.opsForValue().append("msg" ,"hello" ); String msg=stringRedisTemplate.opsForValue().get("msg" ); System.out.println(msg); stringRedisTemplate.opsForList().leftPush("list1" ,"10" ); stringRedisTemplate.opsForList().leftPush("list1" ,"11" ); } @Test void testRedis2 () { Employee employee=employeeMapper.getEmpById(2 ); stringRedisTemplate.opsForValue().set("emp-02" , String.valueOf(employee)); empRedisTemplate.opsForValue().set("emp-01" ,employee); }
结果
1 2 3 4 5 6 7 8 9 10 key:emp-02 value:Employee [id=2 , lastName=lisi, email=null , gender=0 , dId=null ] key:"emp-01" value: { "id" : 2 , "lastName" : "lisi" , "email" : null , "gender" : 0 , "dId" : null }
当存储对象时,默认是使用序列化的方式
RedisTemplate默认使用的序列化器JdkSerializationRedisSerializer
1 2 3 4 5 if (defaultSerializer == null ) { defaultSerializer = new JdkSerializationRedisSerializer( classLoader != null ? classLoader : this .getClass().getClassLoader()); }
可以自己修改默认的序列化规则
1 2 3 4 5 6 7 8 9 10 11 12 13 @Configuration public class MyRedisConfig { @Bean public RedisTemplate<Object, Object> empRedisTemplate (RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer(Object.class); template.setDefaultSerializer(jackson2JsonRedisSerializer); return template; } }
14.RedisTemplate配置1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Configuration public class RedisConfig { @Bean @SuppressWarnings("all") public RedisTemplate<String, Object> redisTemplate (RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); template.setConnectionFactory(factory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); template.setKeySerializer(stringRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); template.setValueSerializer(jackson2JsonRedisSerializer); template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } }
15.Redis的工具类1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 package top.codekiller.manager.common.utils;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Component;import org.springframework.util.CollectionUtils;import java.util.List;import java.util.Map;import java.util.Set;import java.util.concurrent.TimeUnit;@Slf4j public class RedisUtils { @Autowired private RedisTemplate<String, Object> redisTemplate; public boolean expire (String key, long time) { try { if (time > 0 ) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true ; } catch (Exception e) { log.error("指定缓存失效时间失败" ,e); return false ; } } public long getExpire (String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } public boolean hasKey (String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { log.error("判断key是否存在错误" ,e); return false ; } } @SuppressWarnings("unchecked") public void del (String... key) { if (key != null && key.length > 0 ) { if (key.length == 1 ) { redisTemplate.delete(key[0 ]); } else { redisTemplate.delete(CollectionUtils.arrayToList(key)); } } } public Object get (String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } public boolean set (String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true ; } catch (Exception e) { log.error("String类型放入缓存错误" ,e); return false ; } } public boolean set (String key, Object value, long time) { try { if (time > 0 ) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true ; } catch (Exception e) { log.error("String类型放入限时缓存错误" ,e); return false ; } } public long incr (String key, long delta) { if (delta < 0 ) { throw new RuntimeException("递增因子必须大于0" ); } return redisTemplate.opsForValue().increment(key, delta); } public long decr (String key, long delta) { if (delta < 0 ) { throw new RuntimeException("递减因子必须大于0" ); } return redisTemplate.opsForValue().increment(key, -delta); } public Object hget (String key, String item) { return redisTemplate.opsForHash().get(key, item); } public Map<Object, Object> hmget (String key) { return redisTemplate.opsForHash().entries(key); } public boolean hmset (String key, Map<String, Object> map) { try { redisTemplate.opsForHash().putAll(key, map); return true ; } catch (Exception e) { log.error("hash类型在指定的key中放入多个键值缓存错误" ,e); return false ; } } public boolean hmset (String key, Map<String, Object> map, long time) { try { redisTemplate.opsForHash().putAll(key, map); if (time > 0 ) { expire(key, time); } return true ; } catch (Exception e) { log.error("hash类型在指定的key中放入多个限时键值缓存错误" ,e); return false ; } } public boolean hset (String key, String item, Object value) { try { redisTemplate.opsForHash().put(key, item, value); return true ; } catch (Exception e) { log.error("hash类型在指定的key中放入一对键值错误" ,e); return false ; } } public boolean hset (String key, String item, Object value, long time) { try { redisTemplate.opsForHash().put(key, item, value); if (time > 0 ) { expire(key, time); } return true ; } catch (Exception e) { log.error("hash类型在指定的key(限时)中放入一对键值错误" ,e); return false ; } } public void hdel (String key, Object... item) { redisTemplate.opsForHash().delete(key, item); } public boolean hHasKey (String key, String item) { return redisTemplate.opsForHash().hasKey(key, item); } public double hincr (String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, by); } public double hdecr (String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, -by); } public Set<Object> sGet (String key) { try { return redisTemplate.opsForSet().members(key); } catch (Exception e) { log.error("set类型通过指定的key获取值错误" ,e); return null ; } } public boolean sHasKey (String key, Object value) { try { return redisTemplate.opsForSet().isMember(key, value); } catch (Exception e) { log.error("set类型中在指定的key中查询值是否存在错误" ,e); return false ; } } public long sSet (String key, Object... values) { try { return redisTemplate.opsForSet().add(key, values); } catch (Exception e) { log.error("set类型缓存增加错误" ,e); return 0 ; } } public long sSetAndTime (String key, long time, Object... values) { try { Long count = redisTemplate.opsForSet().add(key, values); if (time > 0 ) expire(key, time); return count; } catch (Exception e) { log.error("set类型限时缓存增加错误" ); return 0 ; } } public long sGetSetSize (String key) { try { return redisTemplate.opsForSet().size(key); } catch (Exception e) { log.error("set类型缓存获取长度错误" ,e); return 0 ; } } public long setRemove (String key, Object... values) { try { Long count = redisTemplate.opsForSet().remove(key, values); return count; } catch (Exception e) { log.error("set类型缓存移除值错误" ,e); return 0 ; } } public List<Object> lGet (String key, long start, long end) { try { return redisTemplate.opsForList().range(key, start, end); } catch (Exception e) { log.error("list缓存获取内容错误" ,e); return null ; } } public long lGetListSize (String key) { try { return redisTemplate.opsForList().size(key); } catch (Exception e) { log.error("list缓存获取内容长度失败" ,e); return 0 ; } } public Object lGetIndex (String key, long index) { try { return redisTemplate.opsForList().index(key, index); } catch (Exception e) { log.error("list缓存通过索引获取值失败" ,e); return null ; } } public boolean lSet (String key, Object value) { try { redisTemplate.opsForList().rightPush(key, value); return true ; } catch (Exception e) { log.error("list放入缓存错误" ,e); return false ; } } public boolean lSet (String key, Object value, long time) { try { redisTemplate.opsForList().rightPush(key, value); if (time > 0 ) expire(key, time); return true ; } catch (Exception e) { log.error("list限时放入缓存错误" ,e); return false ; } } public boolean lSet (String key, List<Object> value) { try { redisTemplate.opsForList().rightPushAll(key, value); return true ; } catch (Exception e) { log.error("list类型缓存中增加多个值错误" ,e); return false ; } } public boolean lSet (String key, List<Object> value, long time) { try { redisTemplate.opsForList().rightPushAll(key, value); if (time > 0 ) expire(key, time); return true ; } catch (Exception e) { log.error("缓存中放入list(限时)类型多个值错误" ,e); return false ; } } public boolean lUpdateIndex (String key, long index, Object value) { try { redisTemplate.opsForList().set(key, index, value); return true ; } catch (Exception e) { log.error("list类型中通过索引修改数据错误" ,e); return false ; } } public long lRemove (String key, long count, Object value) { try { Long remove = redisTemplate.opsForList().remove(key, count, value); return remove; } catch (Exception e) { log.error("list类型中移除多个指定值错误" ,e); return 0 ; } } }