Spring Boot尚硅谷
微服务:架构风格(服务微化)
一个应用应该是一组小型服务,可以通过HTTP方式进行互通。
每一个功能元素最终都是一个可独立替换和独立升级的软件单元。
开发环境
jdk1.8
maven 3.3.9(注意官网下载zip格式解压)
Itellij Idea 2017.2.2
SpringBoot 1.5.9.RELEASE
Maven配置
<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> <repositories> <repository> <id>jdk18</id> <name>Repository for JDK 1.8 builds</name> <url>http://www.myhost.com/maven/jdk14</url> <layout>default</layout> <snapshotPolicy>always</snapshotPolicy> </repository> </repositories> </profile> |
IDEA设置Maven
Spring Boot Hello world
①创建一个Maven工程
②导入Spring Boot相关依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> </dependencies> |
event log报错
Unable to import maven project: See logs for details
help——>show log in explorer查看错误日志
2 errors
java.lang.RuntimeException: com.google.inject.CreationException: Unable to create injector, see the following errors:
1) No implementation for org.apache.maven.model.path.PathTranslator was bound.
while locating org.apache.maven.model.path.PathTranslator
for field at org.apache.maven.model.interpolation.AbstractStringBasedModelInterpolator.pathTranslator(Unknown Source)
at org.codehaus.plexus.DefaultPlexusContainer$1.configure(DefaultPlexusContainer.java:350)
2) No implementation for org.apache.maven.model.path.UrlNormalizer was bound.
while locating org.apache.maven.model.path.UrlNormalizer
for field at org.apache.maven.model.interpolation.AbstractStringBasedModelInterpolator.urlNormalizer(Unknown Source)
at org.codehaus.plexus.DefaultPlexusContainer$1.configure(DefaultPlexusContainer.java:350)
解决方案:调整Maven版本。
③.编写一个主程序
package com.wuxinzhe; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @SpringBootApplication来标注一个主程序类,说明这是一个Spring Boot应用 */ @SpringBootApplication public class MainApplication { public static void main(String[] args) { // Spring Boot 应用启动 SpringApplication.run(MainApplication.class, args); } } |
④编写一个Controller处理请求
package com.wuxinzhe.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloWorldController { @RequestMapping("/hello") public String hello() { return "hello"; } } |
⑤简化部署
在pom.xml中配置
<!--这个插件,可以将应用打成一个war包--> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> |
使用maven的package提示如下:
[INFO] — maven-jar-plugin:2.6:jar (default-jar) @ SpringBootHelloworld —
[INFO] Building jar: E:\ideawordspace\SpringBootHelloworld\target\SpringBootHelloworld-1.0-SNAPSHOT.jar
cmd使用java -jar命令运行jar包
C:\Users\Dell\Desktop>java -jar SpringBootHelloworld-1.0-SNAPSHOT.jar
父工程
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> </parent> |
它的父项目是
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>1.5.9.RELEASE</version> <relativePath>../../spring-boot-dependencies</relativePath> </parent> |
导入的依赖
<!-- Add typical dependencies for a web application --> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> |
spring-boot-starter-web
spring-boot-starter:spring boot场景启动器;帮我们导入了web模块正常运行所依赖的组件;如下:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </dependency> </dependencies> |
Spring Boot将所有功能场景都抽取出来,做成一个个starters(启动器),只需要在项目里面引入这些starters,相关场景的依赖就会导入进来,而且版本由springboot自动管理。
主程序类、主入口类
package com.wuxinzhe; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @SpringBootApplication来标注一个主程序类,说明这是一个Spring Boot应用 */ @SpringBootApplication public class MainApplication { public static void main(String[] args) { // Spring Boot 应用启动 SpringApplication.run(MainApplication.class, args); } } |
@SpringBootApplication标注在某个类上说明这个类是SpringBoot的主程序类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} ) public @interface SpringBootApplication { |
@SpringBootConfiguration:SpringBoot的配置类,标注在某个类上,表示这是一个SpringBoot配置类
@SpringBootConfiguration SpringBootConfiguration.class
package org.springframework.boot; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Configuration; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { } |
@Configuration配置类上来标注这个注解,配置类——配置文件;配置类也是容器中的一个组件@Component
@Configuration Configuration.class
package org.springframework.context.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.stereotype.Component; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration { String value() default ""; } |
@EnableAutoConfiguration开启自动配置功能,以前我们需要配置的东西,SpingBoot帮我们自动配置,@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置功能才能生效。
@EnableAutoConfiguration E:\devinstall\apache-maven-3.6.3\repo\org\springframework\boot\spring-boot-autoconfigure\1.5.9.RELEASE\spring-boot-autoconfigure-1.5.9.RELEASE.jar!\org\springframework\boot\autoconfigure\EnableAutoConfiguration.class
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({EnableAutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { |
@AutoConfigurationPackage自动配置包,将主配置类(@SpringBootApplication)的所在包,及下面所有子包里面的所有组件容器扫描进来。
@Import({Registrar.class}),Spring的底层注解@import,给容器中导入一个组件,导入的组件由
org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar.class
@EnableAutoConfiguration EnableAutoConfiguration.class
@Import({EnableAutoConfigurationImportSelector.class}) |
EnableAutoConfigurationImportSelector:导入哪些组件的选择器,将所有需要的组件以全类名的方式返回,这些组件就会被添加到容器中;会给容器导入非常多的自动配置类(xxxAutoConfiguration),这些自动配置类就是给容器中导入这个场景需要的所有组件,并配置好这些组件。
使用向导快速创建Spring Boot应用
默认生成的SpringBoot项目:
①主程序已经生成好了,我们只需要写自己的逻辑
②resources文件夹中目录结构
- static:保存所有静态资源,如js,css,images
- templates:保存所有模板页面;(SpringBoot默认jar包使用欧冠嵌入式的tomcat,默认不支持jsp页面),我们可以使用模板引擎(freemaker、thymeleaf等)
- application.properties:SpringBoot应用的配置文件,可以修改一些默认配置。
配置文件
SpringBoot使用一个全局的配置文件,配置文件名是固定的:
- application.properties
- application.yml
配置文件的作用:修改SpringBoot自动配置的默认值;SpringBoot在底层都给我们自动配置好;YAML是”YAML Ain’t a Markup Language”(YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:”Yet Another Markup Language”(仍是一种标记语言),但为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名。
以前的配置大多都是xxx.xml文件;
YAML以数据为中心,比json,xml更适合做配置文件。
YAML配置例子
server: port: 8081 |
YAML语法
①基本语法
k:空格v:表示一对键值对(空格必须有)
以空格
的缩进来控制层级关系;只要是左对齐
的一列数据,都是同一个层级的,属性和值是大小写敏感的。
server: port: 8081 path: /hello |
②值的写法
字面量:普通的值(数字,字符串,布尔)
k: v字面直接来写,字符串默认不用加上单引号或者双引号
“”:双引号,会转义字符里面的特殊字符
,具有不同于该字符序列单独出现时的语义,如下
name: “zhangshan \n lisi” 输出zhangsan换行lisi
”:单引号;不转义特殊字符,特殊字符最终只是一个普通的字符串数据,name:’zhangshan \n lisi’ 输出zhangsan \n lisi
对象、Map(属性和值)(键值对)
k: v对象还是k: v的方式
friends: lastName: zhangsan age: 20 |
行内写法:
friends: {lastName: zhangsan,age: 20} |
数组(List/Set)
用-值来表示数组中的一个元素
pets: -cat -dog -pig |
行内写法:
pets: [cat,dog,pig] |
yaml配置文件值获取,配置文件值注入
配置文件
person: lastName: 王二麻子 age: 20 boss: true date: 2020/02/18 map: {k1: v1,k2: v2} lists: - 猪 - 猫 - 狗 dog: name: 波比 age: 18 |
javabean Person.java
package com.wuxinzhe.quickstart.bean; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.Date; import java.util.List; import java.util.Map; /** * 将配置文件中配置的每一个值,映射到这个组件中 * @ConfigurationProperties 告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定 * prefix = "person" 配置文件中哪个下面的所有属性一一映射 * 只有这个组件是容器中的组件,才能使用容器中@ConfigurationProperties的功能 */ @Component @ConfigurationProperties(prefix = "person") public class Person { private String lastName; private Integer age; private Boolean boss; private Date date; private Map<String,Object> map; private List<Object> lists; private Dog dog; public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Boolean getBoss() { return boss; } public void setBoss(Boolean boss) { this.boss = boss; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public Map<String, Object> getMap() { return map; } public void setMap(Map<String, Object> map) { this.map = map; } public List<Object> getLists() { return lists; } public void setLists(List<Object> lists) { this.lists = lists; } public Dog getDog() { return dog; } public void setDog(Dog dog) { this.dog = dog; } @Override public String toString() { return "Person{" + "lastName='" + lastName + '\'' + ", age=" + age + ", boss=" + boss + ", date=" + date + ", map=" + map + ", lists=" + lists + ", dog=" + dog + '}'; } } |
单元测试
package com.wuxinzhe.quickstart; import com.wuxinzhe.quickstart.bean.Person; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class QuickstartApplicationTests { @Autowired Person person; @Test public void contextLoads() { System.out.println(person); } } |
properties配置文件乱码
同上yaml内容,properties配置文件写法
person.last-name=王二麻子 person.age=20 person.boss=true person.date=2020/02/18 person.map.k1=v1 person.map.k2=v2 person.lists=猪,猫,狗 person.dog.name=波比2 person.dog.age=18 |
注意:读取时候乱码,通过下图解决即可
@Value和@ConfigurationProperties区别
只是在某个业务逻辑中需要获取以下配置文件的某项值,则使用@Value;如果我们专门写了一个javabean来和配置文件进行映射时,我们就直接使用@ConfigurationProperties。
@PropertySource结合@ConfigurationProperties加载指定配置文件值
package com.wuxinzhe.quickstart.bean; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; import java.util.Date; import java.util.List; import java.util.Map; /** * 将配置文件中配置的每一个值,映射到这个组件中 * * @ConfigurationProperties 告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定 * prefix = "person" 配置文件中哪个下面的所有属性一一映射 * 只有这个组件是容器中的组件,才能使用容器中@ConfigurationProperties的功能 */ @PropertySource(value = {"classpath:person.properties"}) @Component @ConfigurationProperties(prefix = "person") public class Person {} |
@ImportResource加载自定义配置文件
①创建一个service
package com.wuxinzhe.quickstart.service; public class HelloService { } |
②创建一个spring xml配置文件
<?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 class="com.wuxinzhe.quickstart.service.HelloService" id="helloService"> </bean> </beans> |
③主应用@ImportResource加载配置文件
@ImportResource(locations = {"classpath:beans.xml"}) @SpringBootApplication public class QuickstartApplication { public static void main(String[] args) { SpringApplication.run(QuickstartApplication.class, args); } } |
④单元测试
package com.wuxinzhe.quickstart; import com.wuxinzhe.quickstart.bean.Person; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class QuickstartApplicationTests { @Autowired ApplicationContext ioc; @Test public void helloService(){ boolean bool = ioc.containsBean("helloService"); System.out.println(bool); } } |
@Bean和@Configuration配置类将javabean加入容器(推荐使用)
package com.wuxinzhe.quickstart.config; import com.wuxinzhe.quickstart.service.HelloService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MyAppConfig { @Bean public HelloService helloService(){ //helloService为方法名 return new HelloService(); } } |
单元测试
@Autowired ApplicationContext ioc; @Test public void helloService(){ boolean bool = ioc.containsBean("helloService"); // 获取javabean名字对应注入类的方法名 System.out.println(bool); } |
配置文件使用占位符
hasVariable=玻璃 person.last-name=王二麻子 # 随机数 person.age=${random.int} person.boss=true person.date=2020/02/18 person.map.k1=v1 person.map.k2=v2 person.lists=猪,猫,狗 # 引用之前定义的变量 person.dog.name=${hasVariable} # 引用之前定义的变量 未定义给一个默认值 person.dog.age=${dogAge:22} |
多个profile文件支持
resources\application-dev.properties文件
server.port=8084 |
创建resources\application-product.properties文件
server.port=8199 |
resources\application.properties文件
server: port: 8082 spring: profiles: active: product |
服务器启用8199端口
三个短横线,多个profile区(文档快)
server: port: 8082 spring: profiles: active: prod --- spring: profiles: prod server: port: 8777 --- spring: profiles: development server: port: 8999 |
服务器启用8777端口
同上配置文件,采用命令参数,使用development配置
①--spring.profiles.active=development |
②-Dspring.profiles.active=development
③打成jar包后cmd命令行启动
E:\ideawordspace\quickstart\target>java -jar quickstart-0.0.1-SNAPSHOT.jar --spring.profiles.active=development |
配置文件加载位置
①加载优先级
SpringBoot启动会扫描以下位置的application.properties或者application.yml文件作为springboot的默认配置文件
-project/config/配置文件名
-project/配置文件名
-classpath:/config/配置文件名
-classpath:配置文件名
优先级由高到低,同样的内容,优先级高的会覆盖优先级的内容。
application.properties
server.port=8081 #应用的上下文路径,也可以称为项目路径,是构成url地址的一部分。 server.servlet.context-path=/boot |
访问路径就必须为 域名/boot/url
②通过spring.config.location来改变默认的配置文件位置
项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置,指定配置文件和默认加载这些配置文件共同起作用形成互补配置。
C:\Users\Dell\Desktop\application.properties
server.port=8777 |
cmd执行java -jar springboot-config-0.0.1-SNAPSHOT.jar –spring.config.location=C:\Users\Dell\Desktop\application.properties命令
外部配置加载顺序
Springboot也可以从以下位置加载配置;优先级从高到低;高优先级的配置覆盖低优先级的配置,所有的配置会形成互不配置。
1.命令行参数(多个配置用空格隔开 –配置项=值)
E:\ideawordspace\springboot-config\target>java -jar springboot-config-0.0.1-SNAPSHOT.jar –server.port=8092’
2.来自java:comp/env的NDI属性
3.java系统属性(System.getProperties)
4.操作系统环境变量
5.RandomValuePropertySource配置的random.*属性值
由jar包外优先级高于jar包内,然后优先加载带profile的
6.jar包外部的application-{profile-name}.properties或者application.yml(带spring.profile)配置文件
7.jar包内部的application-{profile-name}.properties或者application.yml(带spring.profile)配置文件
8.jar包外部的application-properties或application.yml(不带spring.profile)配置文件
9.jar包内部的application-properties或application.yml(不带spring.profile)配置文件
10.@Configuration注解类上的@PropertySource
11.通过SpringApplication.setDefaultProperties指定的默认属性
自动配置原理(18.自动配置原理++++++++需要多听几遍)
SpringBoot配置文件所有属性(SpringBoot不同版本可能有稍微差异)https://docs.spring.io/spring-boot/docs/2.3.0.BUILD-SNAPSHOT/reference/html/appendix-application-properties.html#common-application-properties
①SpringBoot启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfiguration
org\springframework\boot\autoconfigure\SpringBootApplication.class
@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 { |
②@EnableAutoConfiguration作用
org\springframework\boot\autoconfigure\EnableAutoConfiguration.class
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { |
利用AutoConfigurationImportSelector.class给容器中导入一些组件?
可以通过AutoConfigurationImportSelector中selectImports方法查看
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);//获取候选的配置 (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); |
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); return configurations; } |
扫描所有jar包路径下的META-INF/spring.factories
把扫描到的这些文件的内容包装成properties对象
从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把它们添加到容器中
将类路径下META-INF\spring.factories里面配置的所有EnableAutoConfiguration的值加入到容器中
③spring-boot-autoconfigure-2.2.4.RELEASE.jar
# 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.CloudServiceConnectorsAutoConfiguration,\ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\ org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\ org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\ org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveRestClientAutoConfiguration,\ org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\ org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\ org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\ org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\ org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\ org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\ org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\ org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\ org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\ org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\ org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\ org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\ org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\ org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\ org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\ org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\ org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\ org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\ org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\ org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\ org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\ org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\ org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\ org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\ org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\ org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\ org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\ org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\ org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\ org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\ org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\ org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\ org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\ org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\ org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\ org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\ org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\ org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\ org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration |
每一个这样的xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中,用它们来做自动配置
④以HttpEncodingAutoConfiguration.class(Http编码自动配置)为例来看自动配置原理
@Configuration( proxyBeanMethods = false ) //表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件 @EnableConfigurationProperties({HttpProperties.class}) // 启用指定类的ConfigurationProperties功能,将配置文件中对应的值和HttpProperties绑定起来,并把HttpProperties加入到ioc容器中 @ConditionalOnWebApplication(type = Type.SERVLET) //Spring底层@Conditional注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效,判断当前应用是否是web应用,如果是,当前配置类生效 @ConditionalOnClass({CharacterEncodingFilter.class}) // ConditionalOnClass:当给定的类名在类路径上存在,则实例化当前Bean CharacterEncodingFilter:springMVC进行乱码处理的过滤器 @ConditionalOnProperty( prefix = "spring.http.encoding", value = {"enabled"}, matchIfMissing = true ) //判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的;即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的 public class HttpEncodingAutoConfiguration { //它已经和SpringBoot的配置文件映射了 private final Encoding properties; // 只有一个有参构造器的情况下,参数的值就会从容器中拿 public HttpEncodingAutoConfiguration(HttpProperties properties) { this.properties = properties.getEncoding(); } @Bean // 给容器中添加一个组件,这个组件的某些值需要从properties中获取 @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.所有在配置文件中能配置的属性都是在xxxProperties类中封装着,配置文件能配置什么就可以参照某个功能对应的这个属性类
@ConfigurationProperties(prefix = "spring.http") // 从配置文件中获取指定的值和bean属性进行绑定 public class HttpProperties { |
SpringBoot精髓
1.SpringBoot启动会加载大量的自动配置类
2.我们看我们需要的功能有没有SpringBoot默认写好的自动配置类
3.如果有,我们来看这个自动配置类中到低配置了哪些组件(只要我们要用的组件由,我们就不需要再来配置了)
4.给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,我们就可以在配置文件中指定这些属性值
xxxAutoConfiguration:自动配置类,给容器中添加组件
xxxProperties:封装配置文件中相关属性
自动配置类
自动配置类必须在一定的条件下才能生效。
我们怎么知道哪些自动配置类生效;可以在配置文件application.properties使用debug=true,在控制台就可以查看哪些配置类生效
============================ CONDITIONS EVALUATION REPORT ============================ Positive matches: // 启用 ----------------- AopAutoConfiguration matched: - @ConditionalOnProperty (spring.aop.auto=true) matched (OnPropertyCondition) AopAutoConfiguration.ClassProxyingConfiguration matched: - @ConditionalOnMissingClass did not find unwanted class 'org.aspectj.weaver.Advice' (OnClassCondition) - @ConditionalOnProperty (spring.aop.proxy-target-class=true) matched (OnPropertyCondition) Negative matches: // 未启用 ----------------- ActiveMQAutoConfiguration: Did not match: - @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition) AopAutoConfiguration.AspectJAutoProxyingConfiguration: Did not match: - @ConditionalOnClass did not find required class 'org.aspectj.weaver.Advice' (OnClassCondition) |
SLF4j的使用
日志门面(日志的一个抽象层):SLF4J
日志实现(实现抽象层):Log4j Logback
SpringBoot底层是Spring框架,Spring框架默认是用JCL;SpringBoot选用SLF4j和Logback。
以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里的方法,给系统里面导入slf4j的jar包和logback的实现jar包
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HelloWorld { public static void main(String[] args) { Logger logger = LoggerFactory.getLogger(HelloWorld.class); logger.info("Hello World"); } } |
每一个日志的实现框架都有自己的配置文件,使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文件。
其他日志统一转换为slf4j
如何让系统中所有的日志都统一到slf4j
1.将系统中其他日志框架先排除出去;
2.用中间包来替换原有的日志框架;
3.我们导入slf4j其他的实现
http://www.slf4j.org/images/legacy.png
总结
1.SpringBoot底层也是使用slf4j+logback的方式进行日志记录
2.SpringBoot也把其他的日志都替换成了slf4j
如果我们要引入其他框架,一定要把这个框架的默认日志依赖移除掉。
SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉
SpringBoot默认配置
logging.level设置日志级别,后面跟生效的区域,比如root表示整个项目,也可以设置为某个包下,也可以具体到某个类名(日志级别的值不区分大小写),如
logging.level.root=warn logging.level.com.wuxinzhe=debug //在当前项目下生成日志文件 logging.file=springbootlog.log 指定磁盘路径生成日志文件 logging.file=E:\\springbootlog.log 指定在项目对应的磁盘根目录下创建/spring/log/spring.log文件 logging.path=/spring/log #规定日志在控制台输出格式 logging.pattern.console=%d{yyyy/MM/dd-HH:mm:ss} [%thread] %-5level %logger- %msg%n #规定日志在文件输出格式 logging.pattern.file=%d{yyyy/MM/dd-HH:mm} [%thread] %-5level %logger- %msg%n SpringBoot指定日志文件和日志Profile功能 给类路径下放上每个日志框架自己的配置文件即可,SpringBoot就不使用它默认配置的了。 |
单元测试
package com.wuxinzhe; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class test { Logger logger = LoggerFactory.getLogger(getClass()); @Test public void testLog(){ //日志的级别由低到高 // 可以调整日志的输出级别,日志就只会在这个级别和这个级别以后的高级别生效 logger.trace("这是trace日志..."); logger.debug("这是debug日志"); // 默认采用info级别 logger.info("这是info日志"); logger.warn("这是warn日志"); logger.error("这是error日志"); } } |
SpringBoot指定日志文件
在类路径下放指定日志框架的配置文件就可以了
https://docs.spring.io/spring-boot/docs/2.1.12.RELEASE/reference/html/boot-features-logging.html#boot-features-custom-log-configuration
logback.xml:直接就被日志框架识别了
logback-spring.xml:日志框架就不直接加载日志的配置项,由SpirngBoot来解析日志配置,可以使用SpringBoot高级Profile功能,否则就会报错。
<springProfile name="staging"> <!-- configuration to be enabled when the "staging" profile is active --> <!--可以指定某段配置只在某个环境下生效--> </springProfile> |
logback.xml配置参考
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?> <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。 scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。 debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --> <configuration scan="false" scanPeriod="60 seconds" debug="false"> <!-- 定义日志的根目录 value表示的是打印到哪里的--> <property name="LOG_HOME" value="/logs/"/> > <!-- 定义日志文件名称 value表示的是log的名称--> <property name="appName" value="u-plan"/> <!-- ch.qos.logback.core.ConsoleAppender 表示控制台输出 --> <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> <!-- 日志输出格式:%d表示日期时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %logger{50} 表示logger名字最长50个字符,否则按照句点分割。 %msg:日志消息,%n是换行符 --> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </layout> </appender> <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 --> <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 指定日志文件的名称 --> <file>${LOG_HOME}/${appName}.log</file> <!-- 当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名 TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动 %i:当文件大小超过maxFileSize时,按照i进行文件滚动 --> <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log </fileNamePattern> <!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动, 且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件是, 那些为了归档而创建的目录也会被删除。 --> <MaxHistory>30</MaxHistory> <!-- 当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy --> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>1MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <!-- 日志输出格式:%d表示日期时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %logger{50} 表示logger名字最长50个字符,否则按照句点分割。 %msg:日志消息,%n是换行符 --> <layout class="ch.qos.logback.classic.PatternLayout"> <!--springProfile指定某个环境下生效,如果配置此标签,必须在配置文件中指定环境spring.profiles.active--> <springProfile name="dev"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [%logger{50} : %line ] - %msg%n</pattern> </springProfile> <springProfile name="prod"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [%logger{60} : %line ] - %msg%n</pattern> </springProfile> </layout> </appender> <!-- root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应, 要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的appender和level。 --> <root level="info"> <appender-ref ref="stdout" /> <appender-ref ref="appLogAppender" /> </root> </configuration> |
切换日志框架为log4j
①logback排除掉
②log4j-over-sel4j排除掉
<dependencies> <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> |
③添加sel4j依赖
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </dependency> |
④类路径下添加log4j.properties
#you cannot specify every priority with different file for log4j log4j.rootLogger=debug,stdout,info,debug,warn,error #log4j.rootLogger=info,stdout,error #console log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern= [%d{yyyy-MM-dd HH:mm:ss a}]:%p %l%m%n #info log log4j.logger.info=info log4j.appender.info=org.apache.log4j.DailyRollingFileAppender log4j.appender.info.DatePattern='_'yyyy-MM-dd'.log' log4j.appender.info.File=/src/com/hp/log/info.log log4j.appender.info.Append=true log4j.appender.info.Threshold=INFO log4j.appender.info.layout=org.apache.log4j.PatternLayout log4j.appender.info.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n #debug log log4j.logger.debug=debug log4j.appender.debug=org.apache.log4j.DailyRollingFileAppender log4j.appender.debug.DatePattern='_'yyyy-MM-dd'.log' log4j.appender.debug.File=./src/com/hp/log/debug.log log4j.appender.debug.Append=true log4j.appender.debug.Threshold=DEBUG log4j.appender.debug.layout=org.apache.log4j.PatternLayout log4j.appender.debug.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n #warn log log4j.logger.warn=warn log4j.appender.warn=org.apache.log4j.DailyRollingFileAppender log4j.appender.warn.DatePattern='_'yyyy-MM-dd'.log' log4j.appender.warn.File=./src/com/hp/log/warn.log log4j.appender.warn.Append=true log4j.appender.warn.Threshold=WARN log4j.appender.warn.layout=org.apache.log4j.PatternLayout log4j.appender.warn.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n #error log4j.logger.error=error log4j.appender.error = org.apache.log4j.DailyRollingFileAppender log4j.appender.error.DatePattern='_'yyyy-MM-dd'.log' log4j.appender.error.File = /src/com/hp/log/error.log log4j.appender.error.Append = true log4j.appender.error.Threshold = ERROR log4j.appender.error.layout = org.apache.log4j.PatternLayout log4j.appender.error.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n |
webjars和静态资源的映射规则
org\springframework\boot\autoconfigure\web\ResourceProperties.class
@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; |
org\springframework\boot\autoconfigure\web\servlet\WebMvcAutoConfiguration.class
public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); } else { Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl(); if (!registry.hasMappingForPattern("/webjars/**")) { this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl)); } String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl)); } } } // 配置欢迎页映射 @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext) { WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern()); welcomePageHandlerMapping.setInterceptors(this.getInterceptors()); return welcomePageHandlerMapping; } @Configuration @ConditionalOnProperty( value = {"spring.mvc.favicon.enabled"}, matchIfMissing = true ) public static class FaviconConfiguration implements ResourceLoaderAware { private final ResourceProperties resourceProperties; private ResourceLoader resourceLoader; public FaviconConfiguration(ResourceProperties resourceProperties) { this.resourceProperties = resourceProperties; } public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } @Bean public SimpleUrlHandlerMapping faviconHandlerMapping() { SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); mapping.setOrder(-2147483647); mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", this.faviconRequestHandler())); return mapping; } @Bean public ResourceHttpRequestHandler faviconRequestHandler() { ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler(); requestHandler.setLocations(this.resolveFaviconLocations()); return requestHandler; } private List<Resource> resolveFaviconLocations() { String[] staticLocations = WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations()); List<Resource> locations = new ArrayList(staticLocations.length + 1); Stream var10000 = Arrays.stream(staticLocations); ResourceLoader var10001 = this.resourceLoader; this.resourceLoader.getClass(); var10000.map(var10001::getResource).forEach(locations::add); locations.add(new ClassPathResource("/")); return Collections.unmodifiableList(locations); } } |
1.webjars资源访问 所有/webjars/**都去classpath:/META-INF/resources/webjars/去寻找资源
①.导入依赖(https://www.webjars.org/)
<dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>2.2.0</version> </dependency> |
②.http://localhost:8080/webjars/jquery/2.2.0/jquery.min.js
“/**”访问当前项目的任何资源,静态资源文件夹
2.访问静态资源
classpath:/META-INF/resources/ classpath:/resources/ classpath:/static/ classpath:/public/ / 当前项目的根路径 |
3.欢迎页,静态资源文件夹下所有index.html页面
http://localhost:8080/ 找index.html页面
4.图标(在静态资源文件夹下找)
**/favicon.ico
thymeleaf模板引擎
1.导入thymeleaf依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> |
2.thymeleaf示例
@ConfigurationProperties( prefix = "spring.thymeleaf" ) public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING; // 只要我们把html页面放在类路径下的template里边,就可以渲染 public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html"; private boolean checkTemplate = true; private boolean checkTemplateLocation = true; private String prefix = "classpath:/templates/"; private String suffix = ".html"; private String mode = "HTML"; private Charset encoding; private boolean cache; private Integer templateResolverOrder; private String[] viewNames; private String[] excludedViewNames; private boolean enableSpringElCompiler; private boolean renderHiddenMarkersBeforeCheckboxes; private boolean enabled; private final ThymeleafProperties.Servlet servlet; private final ThymeleafProperties.Reactive reactive; |
3.thymeleaf语法
①获取变量值
获取对象的属性或者方法
示例:${userName}
/* * Access to properties using the point (.). Equivalent to calling property getters. */ ${person.father.name} /* * Access to properties can also be made by using brackets ([]) and writing * the name of the property as a variable or between single quotes. */ ${person['father']['name']} /* * If the object is a map, both dot and bracket syntax will be equivalent to * executing a call on its get(...) method. */ ${countriesByCode.ES} ${personsByName['Stephen Zucchini'].age} /* * Indexed access to arrays or collections is also performed with brackets, * writing the index without quotes. */ ${personsArray[0].name} /* * Methods can be called, even with arguments. */ ${person.createCompleteName()} ${person.createCompleteNameWithSeparator('-')} |
内置基本对象
#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.user}
变量选择表达式Selection Variable Expressions
*{…} 配合th:object使用
<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
@{…}
<!-- Will produce 'http://localhost:8080/gtvg/order/details?orderId=3' (plus rewriting) --> <a href="details.html" th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a> <!-- Will produce '/gtvg/order/details?orderId=3' (plus rewriting) --> <a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a> <!-- Will produce '/gtvg/order/3/details' (plus rewriting) --> <a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a> |
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:
Page 17 of 106
No-Operation: _
All these features can be combined and nested:
'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))
[[…]]会被转义,[(…)]不转义。 变量值为<b>AAA</b> [[${}]] <b>AAA</b> [(${})] <b>AAA</b> |
国际化
SpringMVC国际化步骤
1.编写国际化配置文件
2.使用ResourceBundleMessageResource管理国际化资源文件
3.在页面使用fmt:message取出国际化内容
@Configuration @ConditionalOnMissingBean( value = {MessageSource.class}, search = SearchStrategy.CURRENT ) @AutoConfigureOrder(-2147483648) @Conditional({MessageSourceAutoConfiguration.ResourceBundleCondition.class}) @EnableConfigurationProperties public class MessageSourceAutoConfiguration { private static final Resource[] NO_RESOURCES = new Resource[0]; public MessageSourceAutoConfiguration() { } @Bean @ConfigurationProperties( prefix = "spring.messages" ) public MessageSourceProperties messageSourceProperties() { return new MessageSourceProperties(); } @Bean public MessageSource messageSource(MessageSourceProperties properties) { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); if (StringUtils.hasText(properties.getBasename())) { //设置国际化资源文件的基础名(去掉语言国家代码的) messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename()))); } if (properties.getEncoding() != null) { messageSource.setDefaultEncoding(properties.getEncoding().name()); } messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale()); Duration cacheDuration = properties.getCacheDuration(); if (cacheDuration != null) { messageSource.setCacheMillis(cacheDuration.toMillis()); } messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat()); messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage()); return messageSource; } |
SpringBoot国际化步骤
1.创建国家化文件
如login_en_US.properties
login.btn=login
login.password=Password
login.remember=Remember me
login.tip=please login in
login.username=UserName
2.application.properties中配置
spring.messages.basename=i18n.login |
3.视图文件中引用
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}"></h1> |
通过链接切换国际化
原理:国际化Locale(区域信息对象) LocaleResolver(获取区域信息对象)
org\springframework\boot\autoconfigure\web\servlet\WebMvcAutoConfiguration.class
public LocaleResolver localeResolver() { if (this.mvcProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this.mvcProperties.getLocale()); } else { AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); return localeResolver; } } |
org\springframework\web\servlet\i18n\AcceptHeaderLocaleResolver.class
public Locale resolveLocale(HttpServletRequest request) { Locale defaultLocale = this.getDefaultLocale(); if (defaultLocale != null && request.getHeader("Accept-Language") == null) { return defaultLocale; } else { Locale requestLocale = request.getLocale(); List<Locale> supportedLocales = this.getSupportedLocales(); if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) { Locale supportedLocale = this.findSupportedLocale(request, supportedLocales); if (supportedLocale != null) { return supportedLocale; } else { return defaultLocale != null ? defaultLocale : requestLocale; } } else { return requestLocale; } } } |
默认的就是根据请求头带来的区域信息获取Locale进行国际化
①创建一个实现LocaleResolver的java类
package com.wuxinzhe.springweb.component; import org.springframework.lang.Nullable; import org.springframework.web.servlet.LocaleResolver; import org.thymeleaf.util.StringUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Locale; public class MylocaleResolver implements LocaleResolver { @Override public Locale resolveLocale(HttpServletRequest request) { String 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 httpServletRequest, @Nullable HttpServletResponse httpServletResponse, @Nullable Locale locale) { } } |
②在SpringwebApplication.java注册到容器中
@Bean public LocaleResolver localeResolver(){ // 方法名必须为localeResolver 或者 @Bean("localeResolver") return new MylocaleResolver(); } |
③就可以通过http://localhost:8080/index.html?l=en_US和http://localhost:8080/index.html?l=zh_CN进行中英切换了。
开发期间模板引擎页面修改以后,如何生效
1.禁用模板引擎的缓存spring.thymeleaf.cache=false
2.页面修改完成以后ctrl+f9,重新编译
themeleaf抽取公用部分
fragment定义模板
<footer th:fragment="copy"> © 2011 The Good Thymes Virtual Grocery </footer> |
引入模板三种方式
<body> ... <div th:insert="footer :: copy"></div> <div th:replace="footer :: copy"></div> <div th:include="footer :: copy"></div> </body> |
渲染结果
<body> ... <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> </body> |
①根据ID选择器引用模板
<div th:insert="~{footer :: #copy-section}"></div> |
②根据fragment定义的模板引入模板
<footer th:fragment="copy"> © 2011 The Good Thymes Virtual Grocery </footer> |
向模板传递值
模板页面使用引用模板页面变量
<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 th:class="${activeUrl=='main'?'nav-link active':'nav-link'}" 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> |
引用模板页面
<div th:replace="common/admin_template::#sidebar(activeUrl='main')"></div> |
添加日期参数问题
日期格式化:SpringMVC将页面提交的日期数据需要转换为指定的类型
private Date birth;
默认为1990/11/24这种格式,当然可以在application.properties配置文件中指定日期格式,如
spring.mvc.date-format=yyyy-MM-dd |
curd之更新
<form th:action="@{/emp}" method="POST"> <!-- 发送put请求更新数据 ①springMVC中配置HiddenHttpMethodFilter(springboot中自动配置好了) ②页面中创建一个post表单 ③创建一个input隐藏域 --> <input type="hidden" value="put" name="_method" th:if="${emp!=null}"/> <input type="hidden" th:value="${emp.id}" name="id" th:if="${emp!=null}"/> |
如果高版本springboot无法发送一个put请求,则请开启以下内容
spring.mvc.hiddenmethod.filter.enabled=true
curd之删除
<button class="btn btn-sm btn-danger delBtm" th:attr="del_url=@{/emp/}+${emp.id}" onclick="delEmp(this)">删除</button> <form method="post" id="deleteForm"> <input type="hidden" value="delete" name="_method"> </form> <script> feather.replace() $('.delBtm').click(function(){ $('#deleteForm').attr("action",$(this).attr('del_url')).submit() }) </script> |
———————————————————————————————————————————————————————————————————————————
错误处理&定制错误页面
springboot根据请求头来判断不同客户端,根据不同错误做出不同响应
Chrome浏览器
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
响应数据html页面
postman:
Accept: */*
响应数据:
{ "timestamp": "2020-02-26T03:43:22.227+0000", "status": 404, "error": "Not Found", "message": "No message available", "path": "/cur44" } |
①DefaultErrorAttributes
②BasicErrorController处理默认error请求
@Controller @RequestMapping({"${server.error.path:${error.path:/error}}"}) public class BasicErrorController extends AbstractErrorController { @RequestMapping( produces = {"text/html"} // 产生html数据 ) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = this.getStatus(request); Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); // 去哪个页面作为错误页面,包含页面地址和页面内容 ModelAndView modelAndView = this.resolveErrorView(request, response, status, model); return modelAndView != null ? modelAndView : new ModelAndView("error", model); } @RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { HttpStatus status = this.getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity(status); } else { Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL)); return new ResponseEntity(body, status); } } |
③ErrorPageCustomizer
④DefaultErrorViewResolver
一旦系统出现4xx或者5xx之类的错误,ErrorPageCustomizer就会生效(定制错误的响应规则),就会来到error请求,就会被BasicErrorController处理
①响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { Iterator var5 = this.errorViewResolvers.iterator(); ModelAndView modelAndView; do { if (!var5.hasNext()) { return null; } ErrorViewResolver resolver = (ErrorViewResolver)var5.next(); modelAndView = resolver.resolveErrorView(request, status, model); } while(modelAndView == null); return modelAndView; } protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { Iterator var5 = this.errorViewResolvers.iterator(); ModelAndView modelAndView; do { if (!var5.hasNext()) { return null; } ErrorViewResolver resolver = (ErrorViewResolver)var5.next(); modelAndView = resolver.resolveErrorView(request, status, model); } while(modelAndView == null); return modelAndView; } |
如何定制错误的页面
①有模板引擎的情况下:error/状态码.html(error文件夹下的 状态码.html),发生错误就会来到对应页面
我们可以使用4xx和5xx作为错误页面来匹配这种类型的所有错误页面,精确优先(优先寻找精确的 状态码.html)
页面能获取的信息
timestamp时间戳
status状态码
error错误提示
exception异常对象
message异常消息
errors JSR303数据校验错误的对象
②没有模板引擎就在静态资源文件夹下寻找(无法解析上边的变量)
③都找不到,就来到springboot默认错误处理页面
如何定制错误的json数据
页面无法获取错误变量,请在application.properties中配置server.error.include-exception=true
异常类UserNotExist.java
package com.wuxinzhe.springweb.exception; public class UserNotExist extends RuntimeException{ public UserNotExist(){ super("用户不存在"); } } |
application.properties
server.error.include-exception=true |
控制器
@RequestMapping("/hello") public String hello(Map<String,Object> map,@RequestParam("user") String UserName){ if(UserName.equals("libai")){ throw new UserNotExist(); } map.put("hello","hello world!!!"); map.put("users", Arrays.asList("<b>AAA</b>","李四","王五")); return "hello"; } |
5xx.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> [[${timestamp}]]<br> [[${status}]]<br> [[${error}]]<br> [[${exception}]]<br> [[${message}]]<br> </body> </html> |
浏览器访问http://localhost:8080/hello?user=libai
Wed Feb 26 14:04:31 CST 2020 500 Internal Server Error com.wuxinzhe.springweb.exception.UserNotExist 用户不存在 |
postman访问http://localhost:8080/hello?user=libai
{ "timestamp": "2020-02-26T06:07:43.008+0000", "status": 500, "error": "Internal Server Error", "exception": "com.wuxinzhe.springweb.exception.UserNotExist", "message": "用户不存在", "path": "/hello" } |
将我们定制的数据携带
出现错误以后,会来到/error请求,会被BasicErrorController.class类处理,响应出去的出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController.class抽象类中规定的方法)
①完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;
②页面上能用的数据,或者是json返回能用的数据,都是通过errorAttributes.getErrorAttributes得到;
DefaultErrorAttributes.getErrorAttributes
++++++++++++++++++++++++++++++++完整处理案例
UserNotExist.java
package com.wuxinzhe.springweb.exception; public class UserNotExist extends RuntimeException{ public UserNotExist(){ super("用户不存在"); } } |
MyExceptionHandler.java
package com.wuxinzhe.springweb.controller; import com.wuxinzhe.springweb.exception.UserNotExist; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; @ControllerAdvice public class MyExceptionHandler { // 1.浏览器客户端返回的都是json 没有自适应效果 // @ResponseBody // @ExceptionHandler(UserNotExist.class) // public Map<String, Object> handerException(Exception e){ // Map<String,Object> map = new HashMap<>(); // map.put("code","user not exist"); // map.put("message","用户不存在"); // return map; // } // 可以自适应不同端的请求 但是无法定制数据 // @ExceptionHandler(UserNotExist.class) // public String handerException(Exception e, HttpServletRequest request){ // Map<String,Object> map = new HashMap<>(); // map.put("code","user not exist"); // map.put("message","用户不存在"); // // 传入我们自己的错误状态码 4xx 5xx 否则就不会进入错误定制页面的解析流程 // request.setAttribute("javax.servlet.error.status_code",500); // return "forward:/error"; // } @ExceptionHandler(UserNotExist.class) public String handerException(Exception e, HttpServletRequest request){ Map<String,Object> map = new HashMap<>(); map.put("code","user not exist"); map.put("message","用户不存在"); // 传入我们自己的错误状态码 4xx 5xx 否则就不会进入错误定制页面的解析流程 request.setAttribute("javax.servlet.error.status_code",533); request.setAttribute("ext",map); return "forward:/error"; } } |
MyErrorAttributes.java
package com.wuxinzhe.springweb.component; import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; import org.springframework.stereotype.Component; import org.springframework.web.context.request.WebRequest; import java.util.Map; @Component public class MyErrorAttributes extends DefaultErrorAttributes { // 返回值map就是页面和json能获取的所有字段 @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String,Object> map = super.getErrorAttributes(webRequest, includeStackTrace); map.put("customMsg","自定义内容"); // 获取异常处理器携带的参数 Map<String,Object> ext = (Map<String,Object>) webRequest.getAttribute("ext",0); map.put("ext",ext); return map; } } |
http://localhost:8080/hello?user=libai
{ "timestamp": "2020-02-26T09:40:04.530+0000", "status": 533, "error": "Http Status 533", "message": "用户不存在", "path": "/hello", "customMsg": "自定义内容", "ext": { "code": "user not exist", "message": "用户不存在" } } |
嵌入式servlet容器配置修改
1.application.properties配置文件修改
@ConfigurationProperties( prefix = "server", ignoreUnknownFields = true ) public class ServerProperties { private Integer port; private InetAddress address; @NestedConfigurationProperty private final ErrorProperties error = new ErrorProperties(); private Boolean useForwardHeaders; private String serverHeader; private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8L); private Duration connectionTimeout; @NestedConfigurationProperty private Ssl ssl; @NestedConfigurationProperty private final Compression compression = new Compression(); @NestedConfigurationProperty private final Http2 http2 = new Http2(); private final ServerProperties.Servlet servlet = new ServerProperties.Servlet(); private final ServerProperties.Tomcat tomcat = new ServerProperties.Tomcat(); private final ServerProperties.Jetty jetty = new ServerProperties.Jetty(); private final ServerProperties.Netty netty = new ServerProperties.Netty(); private final ServerProperties.Undertow undertow = new ServerProperties.Undertow(); |
2.通过TomcatWebServerFactoryCustomizer修改配置
@Autowired private Environment environment; @Autowired ServerProperties serverProperties; @Bean public TomcatWebServerFactoryCustomizer tomcatWebServerCustomizer() { serverProperties.setPort(8118); System.out.println("测试服务器启动"+serverProperties.toString()+serverProperties.getPort()); return new TomcatWebServerFactoryCustomizer(environment, serverProperties) { }; } |
注册Servlet三大组件
由于springboot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。
1.注册Servlet
MyServlet.java(定义一个Servlet)
package com.wuxinzhe.springweb.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //resp.setCharacterEncoding("gbk"); doPost(req,resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("servlet console"); resp.setContentType("text/html;charset=utf-8"); resp.getWriter().write("自定义的servlet"); } } |
MyServerConfig.java(ServletRegistrationBean注册Servlet)
@Configuration public class MyServerConfig { @Bean public ServletRegistrationBean myservlet(){ return new ServletRegistrationBean<HttpServlet>(new MyServlet(),"/myservlet"); } } |
2.注册Filter
MyFilter.java类
package com.wuxinzhe.springweb.filter; import javax.servlet.*; import java.io.IOException; public class MyFilter implements Filter{ @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("MyFilter Test"); filterChain.doFilter(servletRequest,servletResponse); } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void destroy() { } } |
MyServerConfig.java
@Configuration public class MyServerConfig { @Autowired private Environment environment; @Autowired ServerProperties serverProperties; @Bean public FilterRegistrationBean filterRegistration(){ FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>(); filterFilterRegistrationBean.setFilter(new MyFilter()); filterFilterRegistrationBean.setUrlPatterns(Arrays.asList("/myservlet","/hello")); return filterFilterRegistrationBean; } } |
3.注册Listener
MyListener.java
public class MyListener implements ServletContextListener{ @Override public void contextInitialized(ServletContextEvent sce) { System.out.println("contextInitialized...web启动"); } @Override public void contextDestroyed(ServletContextEvent sce) { System.out.println("contextDestroyed...当前web项目销毁"); } } |
加入容器中
@Bean public ServletListenerRegistrationBean servletListenerRegistrationBean(){ return new ServletListenerRegistrationBean<MyListener>(new MyListener()); } |
使用其他Servlet容器
引入web默认就是用tomcat做嵌入式servlet容器
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!--<exclusions>--> <!--<exclusion>--> <!--<artifactId>spring-boot-starter-tomcat</artifactId>--> <!--<groupId>org.springframework.boot</groupId>--> <!--</exclusion>--> <!--</exclusions>--> </dependency> 切换为其他servlet容器 // 排除tomcat依赖 <dependencies> <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> // 添加jetty依赖 ,也可以替换为 spring-boot-starter-undertow <dependency> <artifactId>spring-boot-starter-jetty</artifactId> <groupId>org.springframework.boot</groupId> </dependency> |
嵌入式servlet容器自动配置原理
根据不同依赖,注入不同servlet容器
EmbeddedWebServerFactoryCustomizerAutoConfiguration.java
package org.springframework.boot.autoconfigure.web.embedded; import io.undertow.Undertow; import org.apache.catalina.startup.Tomcat; import org.apache.coyote.UpgradeProtocol; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.webapp.WebAppContext; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.xnio.SslClientAuthMode; import reactor.netty.http.server.HttpServer; @Configuration @ConditionalOnWebApplication @EnableConfigurationProperties({ServerProperties.class}) public class EmbeddedWebServerFactoryCustomizerAutoConfiguration { public EmbeddedWebServerFactoryCustomizerAutoConfiguration() { } // Netty @Configuration @ConditionalOnClass({HttpServer.class}) // reactor.netty.http.server.HttpServer public static class NettyWebServerFactoryCustomizerConfiguration { public NettyWebServerFactoryCustomizerConfiguration() { } @Bean public NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) { return new NettyWebServerFactoryCustomizer(environment, serverProperties); } } // Undertow @Configuration @ConditionalOnClass({Undertow.class, SslClientAuthMode.class}) public static class UndertowWebServerFactoryCustomizerConfiguration { public UndertowWebServerFactoryCustomizerConfiguration() { } @Bean public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) { return new UndertowWebServerFactoryCustomizer(environment, serverProperties); } } // Jetty @Configuration @ConditionalOnClass({Server.class, Loader.class, WebAppContext.class}) public static class JettyWebServerFactoryCustomizerConfiguration { public JettyWebServerFactoryCustomizerConfiguration() { } @Bean public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) { return new JettyWebServerFactoryCustomizer(environment, serverProperties); } } // Tomcat @Configuration @ConditionalOnClass({Tomcat.class, UpgradeProtocol.class}) public static class TomcatWebServerFactoryCustomizerConfiguration { public TomcatWebServerFactoryCustomizerConfiguration() { } @Bean public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) { return new TomcatWebServerFactoryCustomizer(environment, serverProperties); } } } |
TomcatWebServerFactoryCustomizer.java
package org.springframework.boot.autoconfigure.web.embedded; import java.time.Duration; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import org.apache.catalina.LifecycleListener; import org.apache.catalina.Valve; import org.apache.catalina.valves.AccessLogValve; import org.apache.catalina.valves.ErrorReportValve; import org.apache.catalina.valves.RemoteIpValve; import org.apache.coyote.AbstractProtocol; import org.apache.coyote.ProtocolHandler; import org.apache.coyote.http11.AbstractHttp11Protocol; import org.springframework.boot.autoconfigure.web.ErrorProperties; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeStacktrace; import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat; import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.Accesslog; import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.Resource; import org.springframework.boot.cloud.CloudPlatform; import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.core.Ordered; import org.springframework.core.env.Environment; import org.springframework.util.StringUtils; import org.springframework.util.unit.DataSize; public class TomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory>, Ordered { private final Environment environment; private final ServerProperties serverProperties; public TomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) { this.environment = environment; this.serverProperties = serverProperties; } public int getOrder() { return 0; } public void customize(ConfigurableTomcatWebServerFactory factory) { ServerProperties properties = this.serverProperties; Tomcat tomcatProperties = properties.getTomcat(); PropertyMapper propertyMapper = PropertyMapper.get(); tomcatProperties.getClass(); propertyMapper.from(tomcatProperties::getBasedir).whenNonNull().to(factory::setBaseDirectory); tomcatProperties.getClass(); propertyMapper.from(tomcatProperties::getBackgroundProcessorDelay).whenNonNull().as(Duration::getSeconds).as(Long::intValue).to(factory::setBackgroundProcessorDelay); this.customizeRemoteIpValve(factory); tomcatProperties.getClass(); propertyMapper.from(tomcatProperties::getMaxThreads).when(this::isPositive).to((maxThreads) -> { this.customizeMaxThreads(factory, tomcatProperties.getMaxThreads()); }); tomcatProperties.getClass(); propertyMapper.from(tomcatProperties::getMinSpareThreads).when(this::isPositive).to((minSpareThreads) -> { this.customizeMinThreads(factory, minSpareThreads.intValue()); }); propertyMapper.from(this::determineMaxHttpHeaderSize).whenNonNull().asInt(DataSize::toBytes).when(this::isPositive).to((maxHttpHeaderSize) -> { this.customizeMaxHttpHeaderSize(factory, maxHttpHeaderSize.intValue()); }); tomcatProperties.getClass(); propertyMapper.from(tomcatProperties::getMaxSwallowSize).whenNonNull().asInt(DataSize::toBytes).to((maxSwallowSize) -> { this.customizeMaxSwallowSize(factory, maxSwallowSize.intValue()); }); tomcatProperties.getClass(); propertyMapper.from(tomcatProperties::getMaxHttpFormPostSize).asInt(DataSize::toBytes).when((maxHttpFormPostSize) -> { return maxHttpFormPostSize.intValue() != 0; }).to((maxHttpFormPostSize) -> { this.customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize.intValue()); }); tomcatProperties.getClass(); propertyMapper.from(tomcatProperties::getAccesslog).when(Accesslog::isEnabled).to((enabled) -> { this.customizeAccessLog(factory); }); tomcatProperties.getClass(); propertyMapper.from(tomcatProperties::getUriEncoding).whenNonNull().to(factory::setUriEncoding); properties.getClass(); propertyMapper.from(properties::getConnectionTimeout).whenNonNull().to((connectionTimeout) -> { this.customizeConnectionTimeout(factory, connectionTimeout); }); tomcatProperties.getClass(); propertyMapper.from(tomcatProperties::getConnectionTimeout).whenNonNull().to((connectionTimeout) -> { this.customizeConnectionTimeout(factory, connectionTimeout); }); tomcatProperties.getClass(); propertyMapper.from(tomcatProperties::getMaxConnections).when(this::isPositive).to((maxConnections) -> { this.customizeMaxConnections(factory, maxConnections.intValue()); }); tomcatProperties.getClass(); propertyMapper.from(tomcatProperties::getAcceptCount).when(this::isPositive).to((acceptCount) -> { this.customizeAcceptCount(factory, acceptCount.intValue()); }); this.customizeStaticResources(factory); this.customizeErrorReportValve(properties.getError(), factory); } private boolean isPositive(int value) { return value > 0; } private DataSize determineMaxHttpHeaderSize() { return this.serverProperties.getTomcat().getMaxHttpHeaderSize().toBytes() > 0L ? this.serverProperties.getTomcat().getMaxHttpHeaderSize() : this.serverProperties.getMaxHttpHeaderSize(); } private void customizeAcceptCount(ConfigurableTomcatWebServerFactory factory, int acceptCount) { factory.addConnectorCustomizers(new TomcatConnectorCustomizer[]{(connector) -> { ProtocolHandler handler = connector.getProtocolHandler(); if (handler instanceof AbstractProtocol) { AbstractProtocol<?> protocol = (AbstractProtocol)handler; protocol.setAcceptCount(acceptCount); } }}); } private void customizeMaxConnections(ConfigurableTomcatWebServerFactory factory, int maxConnections) { factory.addConnectorCustomizers(new TomcatConnectorCustomizer[]{(connector) -> { ProtocolHandler handler = connector.getProtocolHandler(); if (handler instanceof AbstractProtocol) { AbstractProtocol<?> protocol = (AbstractProtocol)handler; protocol.setMaxConnections(maxConnections); } }}); } private void customizeConnectionTimeout(ConfigurableTomcatWebServerFactory factory, Duration connectionTimeout) { factory.addConnectorCustomizers(new TomcatConnectorCustomizer[]{(connector) -> { ProtocolHandler handler = connector.getProtocolHandler(); if (handler instanceof AbstractProtocol) { AbstractProtocol<?> protocol = (AbstractProtocol)handler; protocol.setConnectionTimeout((int)connectionTimeout.toMillis()); } }}); } private void customizeRemoteIpValve(ConfigurableTomcatWebServerFactory factory) { Tomcat tomcatProperties = this.serverProperties.getTomcat(); String protocolHeader = tomcatProperties.getProtocolHeader(); String remoteIpHeader = tomcatProperties.getRemoteIpHeader(); if (StringUtils.hasText(protocolHeader) || StringUtils.hasText(remoteIpHeader) || this.getOrDeduceUseForwardHeaders()) { RemoteIpValve valve = new RemoteIpValve(); valve.setProtocolHeader(StringUtils.hasLength(protocolHeader) ? protocolHeader : "X-Forwarded-Proto"); if (StringUtils.hasLength(remoteIpHeader)) { valve.setRemoteIpHeader(remoteIpHeader); } valve.setInternalProxies(tomcatProperties.getInternalProxies()); valve.setPortHeader(tomcatProperties.getPortHeader()); valve.setProtocolHeaderHttpsValue(tomcatProperties.getProtocolHeaderHttpsValue()); factory.addEngineValves(new Valve[]{valve}); } } private boolean getOrDeduceUseForwardHeaders() { if (this.serverProperties.isUseForwardHeaders() != null) { return this.serverProperties.isUseForwardHeaders().booleanValue(); } else { CloudPlatform platform = CloudPlatform.getActive(this.environment); return platform != null && platform.isUsingForwardHeaders(); } } private void customizeMaxThreads(ConfigurableTomcatWebServerFactory factory, int maxThreads) { factory.addConnectorCustomizers(new TomcatConnectorCustomizer[]{(connector) -> { ProtocolHandler handler = connector.getProtocolHandler(); if (handler instanceof AbstractProtocol) { AbstractProtocol protocol = (AbstractProtocol)handler; protocol.setMaxThreads(maxThreads); } }}); } private void customizeMinThreads(ConfigurableTomcatWebServerFactory factory, int minSpareThreads) { factory.addConnectorCustomizers(new TomcatConnectorCustomizer[]{(connector) -> { ProtocolHandler handler = connector.getProtocolHandler(); if (handler instanceof AbstractProtocol) { AbstractProtocol protocol = (AbstractProtocol)handler; protocol.setMinSpareThreads(minSpareThreads); } }}); } private void customizeMaxHttpHeaderSize(ConfigurableTomcatWebServerFactory factory, int maxHttpHeaderSize) { factory.addConnectorCustomizers(new TomcatConnectorCustomizer[]{(connector) -> { ProtocolHandler handler = connector.getProtocolHandler(); if (handler instanceof AbstractHttp11Protocol) { AbstractHttp11Protocol protocol = (AbstractHttp11Protocol)handler; protocol.setMaxHttpHeaderSize(maxHttpHeaderSize); } }}); } private void customizeMaxSwallowSize(ConfigurableTomcatWebServerFactory factory, int maxSwallowSize) { factory.addConnectorCustomizers(new TomcatConnectorCustomizer[]{(connector) -> { ProtocolHandler handler = connector.getProtocolHandler(); if (handler instanceof AbstractHttp11Protocol) { AbstractHttp11Protocol<?> protocol = (AbstractHttp11Protocol)handler; protocol.setMaxSwallowSize(maxSwallowSize); } }}); } private void customizeMaxHttpFormPostSize(ConfigurableTomcatWebServerFactory factory, int maxHttpFormPostSize) { factory.addConnectorCustomizers(new TomcatConnectorCustomizer[]{(connector) -> { connector.setMaxPostSize(maxHttpFormPostSize); }}); } private void customizeAccessLog(ConfigurableTomcatWebServerFactory factory) { Tomcat tomcatProperties = this.serverProperties.getTomcat(); AccessLogValve valve = new AccessLogValve(); valve.setPattern(tomcatProperties.getAccesslog().getPattern()); valve.setDirectory(tomcatProperties.getAccesslog().getDirectory()); valve.setPrefix(tomcatProperties.getAccesslog().getPrefix()); valve.setSuffix(tomcatProperties.getAccesslog().getSuffix()); valve.setRenameOnRotate(tomcatProperties.getAccesslog().isRenameOnRotate()); valve.setFileDateFormat(tomcatProperties.getAccesslog().getFileDateFormat()); valve.setRequestAttributesEnabled(tomcatProperties.getAccesslog().isRequestAttributesEnabled()); valve.setRotatable(tomcatProperties.getAccesslog().isRotate()); valve.setBuffered(tomcatProperties.getAccesslog().isBuffered()); factory.addEngineValves(new Valve[]{valve}); } private void customizeStaticResources(ConfigurableTomcatWebServerFactory factory) { Resource resource = this.serverProperties.getTomcat().getResource(); factory.addContextCustomizers(new TomcatContextCustomizer[]{(context) -> { context.addLifecycleListener((event) -> { if (event.getType().equals("configure_start")) { context.getResources().setCachingAllowed(resource.isAllowCaching()); if (resource.getCacheTtl() != null) { long ttl = resource.getCacheTtl().toMillis(); context.getResources().setCacheTtl(ttl); } } }); }}); } private void customizeErrorReportValve(ErrorProperties error, ConfigurableTomcatWebServerFactory factory) { if (error.getIncludeStacktrace() == IncludeStacktrace.NEVER) { factory.addContextCustomizers(new TomcatContextCustomizer[]{(context) -> { ErrorReportValve valve = new ErrorReportValve(); valve.setShowServerInfo(false); valve.setShowReport(false); context.getParent().getPipeline().addValve(valve); }}); } } } |
——————————————————————————————————————————————————————————
自动配置服务器工厂类
ServletWebServerFactoryAutoConfiguration.java
package org.springframework.boot.autoconfigure.web.servlet; import javax.servlet.ServletRequest; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration.EmbeddedJetty; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration.EmbeddedTomcat; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration.EmbeddedUndertow; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.server.ErrorPageRegistrarBeanPostProcessor; import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.ObjectUtils; @Configuration @AutoConfigureOrder(-2147483648) @ConditionalOnClass({ServletRequest.class}) // 仅基于servlet的Web应用程序 @ConditionalOnWebApplication( type = Type.SERVLET ) // ServerProperties 配置中包括了常见的 server.port 等配置属性 @EnableConfigurationProperties({ServerProperties.class}) // 通过 @Import 导入嵌入式容器相关的自动配置类,有 EmbeddedTomcat、EmbeddedJetty 和EmbeddedUndertow。 @Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class}) public class ServletWebServerFactoryAutoConfiguration { public ServletWebServerFactoryAutoConfiguration() { } @Bean public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) { return new ServletWebServerFactoryCustomizer(serverProperties); } @Bean @ConditionalOnClass( name = {"org.apache.catalina.startup.Tomcat"} ) public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) { return new TomcatServletWebServerFactoryCustomizer(serverProperties); } public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware { private ConfigurableListableBeanFactory beanFactory; public BeanPostProcessorsRegistrar() { } public void setBeanFactory(BeanFactory beanFactory) throws BeansException { if (beanFactory instanceof ConfigurableListableBeanFactory) { this.beanFactory = (ConfigurableListableBeanFactory)beanFactory; } } public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (this.beanFactory != null) { //WebServerFactoryCustomizerBeanPostProcessor:作用是在 WebServerFactory 初始化时调用上面自动配置类注入的那些 WebServerFactoryCustomizer,然后调用 WebServerFactoryCustomizer 中的 customize 方法来处理WebServerFactory。 this.registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class); this.registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class); } } private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) { if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) { RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass); beanDefinition.setSynthetic(true); registry.registerBeanDefinition(name, beanDefinition); } } } } |
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { private ListableBeanFactory beanFactory; private List<WebServerFactoryCustomizer<?>> customizers; public WebServerFactoryCustomizerBeanPostProcessor() { } public void setBeanFactory(BeanFactory beanFactory) { Assert.isInstanceOf(ListableBeanFactory.class, beanFactory, "WebServerCustomizerBeanPostProcessor can only be used with a ListableBeanFactory"); this.beanFactory = (ListableBeanFactory)beanFactory; } 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; } // // 这段代码就是拿到所有的 Customizers ,然后遍历调用这些 Customizers 的 customize 方法 private void postProcessBeforeInitialization(WebServerFactory webServerFactory) { ((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> { customizer.customize(webServerFactory); }); } private Collection<WebServerFactoryCustomizer<?>> getCustomizers() { if (this.customizers == null) { this.customizers = new ArrayList(this.getWebServerFactoryCustomizerBeans()); this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE); this.customizers = Collections.unmodifiableList(this.customizers); } return this.customizers; } private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() { return this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values(); } } |
上面这段代码中,注册了两个 bean,一个 WebServerFactoryCustomizerBeanPostProcessor,一个 errorPageRegistrarBeanPostProcessor;这两个都实现类 BeanPostProcessor 接口,属于 bean 的后置处理器,作用是在 bean 初始化前后加一些自己的逻辑处理。
WebServerFactoryCustomizerBeanPostProcessor:作用是在 WebServerFactory 初始化时调用上面自动配置类注入的那些 WebServerFactoryCustomizer ,然后调用 WebServerFactoryCustomizer 中的 customize 方法来 处理 WebServerFactory。
errorPageRegistrarBeanPostProcessor:和上面的作用差不多,不过这个是处理 ErrorPageRegistrar 的。
下面简单看下 WebServerFactoryCustomizerBeanPostProcessor 中的代码:
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { // 省略部分代码 // 在 postProcessBeforeInitialization 方法中,如果当前 bean 是 WebServerFactory,则进行 // 一些后置处理 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof WebServerFactory) { postProcessBeforeInitialization((WebServerFactory) bean); } return bean; } // 这段代码就是拿到所有的 Customizers ,然后遍历调用这些 Customizers 的 customize 方法 private void postProcessBeforeInitialization(WebServerFactory webServerFactory) { LambdaSafe .callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory) .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class) .invoke((customizer) -> customizer.customize(webServerFactory)); } } |
自动配置类中注册的两个 Customizer Bean,这两个 Customizer 实际上就是去处理一些配置值,然后绑定到 各自的工厂类的。
WebServerFactoryCustomizer
将 serverProperties 配置值绑定给 ConfigurableServletWebServerFactory 对象实例上。
ServletWebServerFactoryCustomizer.java
@Override public void customize(ConfigurableServletWebServerFactory factory) { PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); // 端口 map.from(this.serverProperties::getPort).to(factory::setPort); // address map.from(this.serverProperties::getAddress).to(factory::setAddress); // contextPath map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath); // displayName map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName); // session 配置 map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession); // ssl map.from(this.serverProperties::getSsl).to(factory::setSsl); // jsp map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp); // 压缩配置策略实现 map.from(this.serverProperties::getCompression).to(factory::setCompression); // http2 map.from(this.serverProperties::getHttp2).to(factory::setHttp2); // serverHeader map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader); // contextParameters map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters); } |
TomcatServletWebServerFactoryCustomizer相比于上面那个,这个customizer主要处理Tomcat相关的配置值
public void customize(TomcatServletWebServerFactory factory) { // // 拿到 tomcat 相关的配置 Tomcat tomcatProperties = this.serverProperties.getTomcat(); if (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) { factory.getTldSkipPatterns().addAll(tomcatProperties.getAdditionalTldSkipPatterns()); } if (tomcatProperties.getRedirectContextRoot() != null) { this.customizeRedirectContextRoot(factory, tomcatProperties.getRedirectContextRoot().booleanValue()); } if (tomcatProperties.getUseRelativeRedirects() != null) { this.customizeUseRelativeRedirects(factory, tomcatProperties.getUseRelativeRedirects().booleanValue()); } } |
——————————————————————————————————————————————————————————
/** ServletWebServerFactoryConfiguration是一个针对ServletWebServerFactory进行配置的配置类。它通过检测应用classpath存在的类,从而判断当前应用要使用哪个Servlet容器:Tomcat,Jetty还是Undertow。检测出来之后,定义相应的Servlet Web服务器工厂组件bean **/ ServletWebServerFactoryConfiguration.java package org.springframework.boot.autoconfigure.web.servlet; import io.undertow.Undertow; import javax.servlet.Servlet; import org.apache.catalina.startup.Tomcat; import org.apache.coyote.UpgradeProtocol; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.webapp.WebAppContext; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.xnio.SslClientAuthMode; @Configuration class ServletWebServerFactoryConfiguration { ServletWebServerFactoryConfiguration() { } @Configuration @ConditionalOnClass({Servlet.class, Undertow.class, SslClientAuthMode.class}) @ConditionalOnMissingBean( value = {ServletWebServerFactory.class}, search = SearchStrategy.CURRENT ) static class EmbeddedUndertow { EmbeddedUndertow() { } @Bean public UndertowServletWebServerFactory undertowServletWebServerFactory() { return new UndertowServletWebServerFactory(); } } @Configuration @ConditionalOnClass({Servlet.class, Server.class, Loader.class, WebAppContext.class}) @ConditionalOnMissingBean( value = {ServletWebServerFactory.class}, search = SearchStrategy.CURRENT ) static class EmbeddedJetty { EmbeddedJetty() { } @Bean public JettyServletWebServerFactory JettyServletWebServerFactory() { return new JettyServletWebServerFactory(); } } @Configuration @ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class}) @ConditionalOnMissingBean( value = {ServletWebServerFactory.class}, search = SearchStrategy.CURRENT ) static class EmbeddedTomcat { EmbeddedTomcat() { } @Bean public TomcatServletWebServerFactory tomcatServletWebServerFactory() { return new TomcatServletWebServerFactory(); } } } |
—————————————————————————————————————————————————————————————————
嵌入式Servlet启动原理
①主程序调用run方法
public static void main(String[] args) { SpringApplication.run(SpringwebApplication.class, args); } |
② context = this.createApplicationContext();
Springboot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个组件】,如果是web应用创建AnnotationConfigServletWebServerApplicationContext,如下: 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"); } |
③刷新IOC容器this.refreshContext(context);
AbstractApplicationContext.java
public void refresh() throws BeansException, IllegalStateException { Object var1 = this.startupShutdownMonitor; synchronized(this.startupShutdownMonitor) { this.prepareRefresh(); ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory(); this.prepareBeanFactory(beanFactory); try { this.postProcessBeanFactory(beanFactory); this.invokeBeanFactoryPostProcessors(beanFactory); this.registerBeanPostProcessors(beanFactory); this.initMessageSource(); this.initApplicationEventMulticaster(); this.onRefresh(); this.registerListeners(); this.finishBeanFactoryInitialization(beanFactory); this.finishRefresh(); } catch (BeansException var9) { if (this.logger.isWarnEnabled()) { this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9); } this.destroyBeans(); this.cancelRefresh(var9); throw var9; } finally { this.resetCommonCaches(); } } } |
④onRefresh();
webioc容器AnnotationConfigServletWebServerApplicationContext的父类 ServletWebServerApplicationContext.class重写了onRefresh方法
protected void onRefresh() { super.onRefresh(); try { this.createWebServer(); } catch (Throwable var2) { throw new ApplicationContextException("Unable to start web server", var2); } } |
⑤this.createWebServer(); 创建嵌入式web容器
private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = this.getServletContext(); if (webServer == null && servletContext == null) { // 从ioc容器中获取ServletWebServerFactory ServletWebServerFactory factory = this.getWebServerFactory(); this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()}); } else if (servletContext != null) { try { this.getSelfInitializer().onStartup(servletContext); } catch (ServletException var4) { throw new ApplicationContextException("Cannot initialize servlet context", var4); } } this.initPropertySources(); } |
public WebServer getWebServer(ServletContextInitializer... initializers) { Tomcat tomcat = new Tomcat(); File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); 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); } |
⑥获取嵌入式的Servlet容器工厂 getWebServerFactory
protected ServletWebServerFactory getWebServerFactory() { String[] beanNames = this.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class); if (beanNames.length == 0) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean."); } else if (beanNames.length > 1) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames)); } else { return (ServletWebServerFactory)this.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class); } } |
从ioc容器中获取ServletWebServerFactory,就会惊动后置处理器,后置处理器就会获取所有定制器来先定制Servlet的相关配置。
⑦嵌入式的Servlet容器创建对象并启动Servlet容器
使用外部Servlet容器&JSP支持
嵌入式Servlet容器:应用打成Jar包,默认不支持JSP
外部Servlet容器:外部安装的Tomcat容器->应用war包的方式打包,步骤如下
①new project=》springinitializr=》选择packaging为war
②Project Structure=》Modules=》Web=》Web Resources Directories=》创建webapp文件夹 E:\ideawordspace\wartest\src\main\webapp
Deployment Descriptors 添加web.xml文件 E:\ideawordspace\wartest\src\main\webapp\WEB-INF\web.xml
③edit configuration配置web容器
④application.properties配置
spring.mvc.view.prefix=/WEB-INF/pages/
spring.mvc.view.suffix=.jsp
创建一个控制器,映射url http://localhost:8081/helloworld
package com.springboot.wartest.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HelloController { @RequestMapping("helloworld") public String helloWorld(){ return "helloworld"; } } |
完整项目路径
外部Servlet容器启动SpringBoot应用原理
ServletInitializer继承SpringBootServletInitializer重写configure方法
jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器
war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器
servlet3.0
规则:
1.服务器启动(web应用启动)会创建当前web应用里的每一个jar包里面ServletContainerInitializer的实例
2.ServletContainerInitializer的实现放在jar包META-INF/services文件夹下,文件夹下有一个文件名为javax.servlet.ServletContainerInitializer,内容就是ServletContainerInitializer的实现类的全类名。
3.还可以使用@HandleTypes注解,在我们应用启动的时候加载我们感兴趣的类。
流程:
1.启动tomcat
2.org\springframework\spring-web\5.2.4.RELEASE\spring-web-5.2.4.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer
3.ServletContainerInitializer中将@HandlesTypes({WebApplicationInitializer.class})标注的所有这个类型的类都传入到onStartup方法中的@Nullable Set
4.每一个WebApplicationInitializer都调用自己的onStartup方法
5.相当于SpringBootServletInitializer类会被创建对象,并执行onStartup方法
6.SpringBootServletInitializer示例执行onStartup的时候会createRootApplicationContext创建容器
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) { // 1.创建spring应用的构造器 SpringApplicationBuilder SpringApplicationBuilder builder = this.createSpringApplicationBuilder(); builder.main(this.getClass()); ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext); if (parent != null) { this.logger.info("Root context already created (using as parent)."); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null); builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)}); } builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)}); builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class); // 2.调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入进来 builder = this.configure(builder); builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext, null)}); SpringApplication application = builder.build(); if (application.getAllSources().isEmpty() && MergedAnnotations.from(this.getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) { application.addPrimarySources(Collections.singleton(this.getClass())); } Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation"); if (this.registerErrorPageFilter) { application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class)); } return this.run(application); } |
7. spring的应用就启动并且创建IOC容器
application.yml配置数据源
spring: datasource: username: root password: 123456 url: jdbc:mysql://111.230.6.126:3307/test driver-class-name: com.mysql.cj.jdbc.Driver |
单元测试
@Autowired private DataSource dataSource; @Test void datasourceTest() throws SQLException { System.out.println(dataSource.getClass()); Connection connection = dataSource.getConnection(); System.out.println(connection); connection.close(); } |
输出如下:
class com.zaxxer.hikari.HikariDataSource HikariProxyConnection@1789376127 wrapping com.mysql.cj.jdbc.ConnectionImpl@6bcc3f27 |
数据源自动配置原理
springframework\boot\spring-boot-autoconfigure\2.2.5.RELEASE\spring-boot-autoconfigure-2.2.5.RELEASE.jar!\org\springframework\boot\autoconfigure\jdbc\DataSourceConfiguration.class
@Configuration( proxyBeanMethods = false ) @ConditionalOnClass({HikariDataSource.class}) @ConditionalOnMissingBean({DataSource.class}) @ConditionalOnProperty( name = {"spring.datasource.type"}, havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true ) static class Hikari { Hikari() { } @Bean @ConfigurationProperties( prefix = "spring.datasource.hikari" ) HikariDataSource dataSource(DataSourceProperties properties) { HikariDataSource dataSource = (HikariDataSource)DataSourceConfiguration.createDataSource(properties, HikariDataSource.class); if (StringUtils.hasText(properties.getName())) { dataSource.setPoolName(properties.getName()); } return dataSource; } } |
自定义数据源
@Configuration( proxyBeanMethods = false ) @ConditionalOnMissingBean({DataSource.class}) @ConditionalOnProperty( name = {"spring.datasource.type"} ) static class Generic { Generic() { } @Bean DataSource dataSource(DataSourceProperties properties) { // 使用DataSourceBuilder创建数据源,利用反射创建响应type的数据源,并且绑定相关属性 return properties.initializeDataSourceBuilder().build(); } } |
org\springframework\boot\autoconfigure\jdbc\DataSourceInitializer.class
createSchema建表
runScripts 执行sql语句
private List<Resource> getScripts(String propertyName, List<String> resources, String fallback) { if (resources != null) { return this.getResources(propertyName, resources, true); } else { String platform = this.properties.getPlatform(); List<String> fallbackResources = new ArrayList(); fallbackResources.add("classpath*:" + fallback + "-" + platform + ".sql"); fallbackResources.add("classpath*:" + fallback + ".sql"); return this.getResources(propertyName, fallbackResources, false); } } |
schema
默认sql文件名schema.sql或者schema-all.sql,也可以通过在yml,类似如下,自定义多个文件
schema:
-classpath:xxx.sql
JdbcTemplate的简单使用
public class JdbcController { @Autowired JdbcTemplate jdbcTemplate; @ResponseBody @GetMapping("/query") public Map<String,Object> getEmp(){ List<Map<String,Object>> lists = jdbcTemplate.queryForList("SELECT * FROM department"); return lists.get(0); } } |
整合Druid和配置数据源监控
①引入依赖
<!--引入druid--> <!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.20</version> </dependency> |
②application.yml配置数据源类型type
spring: datasource: username: root password: 123456 url: jdbc:mysql://111.230.6.126:3307/test driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource # Druid 数据源其他配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 |
③Configuration 包中编写 druid 监控类
package com.springbootjdbctest.demo.config; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.support.http.StatViewServlet; import com.alibaba.druid.support.http.WebStatFilter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; import javax.sql.DataSource; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @Configuration public class DruidConfig { @ConfigurationProperties(prefix = "spring.datasource") @Bean public DataSource druid(){ return new DruidDataSource(); } // 配置Druid的监控 // 1.配置一个管理后台的Servlet @Bean public ServletRegistrationBean druidStatViewServlet() { ServletRegistrationBean<StatViewServlet> servletRegistrationBean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*"); Map<String, String> initParams = new HashMap<>(); // 可配的属性都在 StatViewServlet 和其父类下 initParams.put("loginUsername", "admin-druid"); initParams.put("loginPassword", "111111"); servletRegistrationBean.setInitParameters(initParams); return servletRegistrationBean; } // 2.配置一个监控的Filter @Bean public FilterRegistrationBean druidWebStatFilter() { FilterRegistrationBean<WebStatFilter> webStatFilterFilterRegistrationBean = new FilterRegistrationBean<>(new WebStatFilter()); Map<String, String> initParams = new HashMap<>(); initParams.put("exclusions", "*.js,*.css,/druid/*"); webStatFilterFilterRegistrationBean.setInitParameters(initParams); webStatFilterFilterRegistrationBean.setUrlPatterns(Arrays.asList("/*")); return webStatFilterFilterRegistrationBean; } } |
通过 http://127.0.0.1:8080/druid/login.html 即可查看Druid监控数据
遇到问题:
*************************** APPLICATION FAILED TO START *************************** Description: Failed to bind properties under 'spring.datasource' to javax.sql.DataSource: Property: spring.datasource.filters Value: stat,wall,log4j Origin: class path resource [application.yml]:20:14 Reason: org.apache.log4j.Logger Action: Update your application's configuration |
解决方案,引入log4j依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> |
参考网址:
https://blog.csdn.net/xingkongtianma01/article/details/81624313
整合Mybatis基础设置
SpringInitialer
选中 Web Mysql JDBC MyBatis模块
引入依赖
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.1</version> </dependency> |
Mybatis注解版
DepartmentMapper.java
package com.springboot.mybatis.mapper; import com.springboot.mybatis.bean.Department; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Options; import org.apache.ibatis.annotations.Select; @Mapper public interface DepartmentMapper { @Select("SELECT * FROM department WHERE id=#{id}") public Department getDepartmentById(Integer id); @Options(useGeneratedKeys=true,keyProperty = "id") @Insert("INSERT INTO department(departmentName) VALUES (#{departmentName})") public int insertDepartment(Department department); } |
开启字段下划线和属性驼峰映射
package com.springboot.mybatis.config; import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class BatisConfig { @Bean public ConfigurationCustomizer configurationCustomizer(){ return new ConfigurationCustomizer() { @Override public void customize(org.apache.ibatis.session.Configuration configuration) { // 字段属性和javabean开启驼峰映射 configuration.setMapUnderscoreToCamelCase(true); } }; } } |
批量扫描Mapper文件
@MapperScan(basePackages="com.springboot.mybatis.mapper") @SpringBootApplication public class MybatisApplication { public static void main(String[] args) { SpringApplication.run(MybatisApplication.class, args); } } |
测试
package com.springboot.mybatis.controller; import com.springboot.mybatis.bean.Department; import com.springboot.mybatis.mapper.DepartmentMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class DepartmentController { @Autowired DepartmentMapper departmentMapper; @ResponseBody @GetMapping("/department") public Department department(Department department){ departmentMapper.insertDepartment(department); return department; } @ResponseBody @GetMapping("/department/{id}") public Department department(@PathVariable("id") Integer id){ Department department = departmentMapper.getDepartmentById(id); return department; } } |
MyBatis配置版
application.yml配置mybatis配置文件和Sql映射文件位置
mybatis: config-location: classpath:mybatis/mybatis-config.xml mapper-locations: mybatis/mapper/*.xml |
src\main\resources\mybatis\mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <!-- javabean属性名驼峰str1Str2 自动对应 数据表字段 str1_str2 --> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> </configuration> |
com\springboot\mybatis\mapper\EmployeeMapper.java
package com.springboot.mybatis.mapper; import com.springboot.mybatis.bean.Employee; import org.apache.ibatis.annotations.Mapper; @Mapper public interface EmployeeMapper { public Employee getEmpById(Integer id); public int insertEmp(Employee employee); } |
src\main\resources\mybatis\mapper\EmployeeMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.springboot.mybatis.mapper.EmployeeMapper"> <select id="getEmpById" resultType="com.springboot.mybatis.bean.Employee"> select * from employee where id = #{id} </select> <insert id="insertEmp" useGeneratedKeys="true" keyProperty="id"> INSERT INTO employee(lastName,email,gender,d_id) VALUES (#{lastName},#{email},#{gender},#{dId}) </insert> </mapper> |
SpringBoot整合JPA
Spring Data是一个用于简化数据库访问,并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷。可以极大的简化JPA的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了CRUD外,还包括如分页、排序等一些常用的功能。
1.引入spring-boot-starter-data-jpa
2.配置文件打印sql语句
3.创建entity标注JPA注解
4.创建Repository接口继承JpaRepository
5.测试方法
spring Initializer => Web、JPA Mysql JDBC模块
报错:java.sql.SQLException: The server time zone value ‘�й���ʱ��’ is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the ‘serverTimezone’ configuration property) to use a more specifc time zone value if you want to utilize time zone support.
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) ~[mysql-connector-java-8.0.19.jar:8.0.19]
解决方案:https://blog.csdn.net/qq_43371004/article/details/98385445第二种方案
报错:com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.springboot.springdatajpa.entity.User$HibernateProxy$5KWlGNm8[“hibernateLazyInitializer”])
解决方案:https://blog.csdn.net/liu_yulong/article/details/84594771
1.引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> |
2.配置文件application.yml
spring: datasource: username: root password: root url: jdbc:mysql://127.0.0.1:3306/tx?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: #更新或者创建数据表结构 ddl-auto: update show-sql: true |
3.实体类src\main\java\com\springboot\springdatajpa\entity\User.java
package com.springboot.springdatajpa.entity; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import javax.persistence.*; @JsonIgnoreProperties(value = {"hibernateLazyInitializer", "handler"}) @Table(name="USER") @Entity public class User { private Integer id; private String lastName; private String email; @GeneratedValue @Id public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } @Column(name="last_name") public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public String toString() { return "User{" + "id=" + id + ", lastName='" + lastName + '\'' + ", email='" + email + '\'' + '}'; } } |
4.继承自JpaRepository接口类
package com.springboot.springdatajpa.repository; import com.springboot.springdatajpa.entity.User; import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository<User,Integer>{ } |
5.测试方法
@Controller public class UserController { @Autowired UserRepository userRepository; @ResponseBody @GetMapping("/user/{id}") public User getUserById(@PathVariable("id") Integer id){ User user = userRepository.getOne(1); return user; } } |
SpringBoot缓存
Java 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对。
每一个存储在Cache中的条目有一个定义的有效期,即Expiry Duration。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。
SpringInitializer选择Cache、Web、Mysql 和 MyBatis模块
引入缓存依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> |
缓存工作原理,@Cacheable运行流程
1.自动配置类
CacheAutoConfiguration.class
2.缓存配置类
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration(默认生效的配置类)
org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
3.给容器中注册一个CacheManager:ConcurrentMapCacheManager
4.可以获取和创建ConcurrentMapCache类型的缓存组件,它的作用是将数据保存在ConcurrentMap中
运行流程:
1.方法运行之前,先去查询Cache缓存组件,按照cacheNames 指定的名字获取,获取缓存,如果没有,会自动创建出来
2.去Cache中查找缓存的内容,使用一个key,默认就是方法和参数,key是按照某种策略生成的,默认是使用SimpleKeyGenerator生成key
SimpleKeyGenerator生成key的默认策略:如果没有参数,则 new SimpleKey(new Object[0]);如果只有一个参数,则key=参数的值;如果有多个参数,new SimpleKey(params)。
3.没有查到缓存,就调用目标方法,并且将目标方法缓存的结果放进缓存中
@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,如果没有就运行方法并将结果放入缓存,以后再来调用就可以直接使用缓存中的数据
核心:
1.使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
2.key使用keyGenerator生成的,默认是SimpleKeyGenerator
RedisTemplate&序列化机制
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> |
使用redis做缓存
application.properties
spring.datasource.username=root spring.datasource.password=root spring.datasource.url=jdbc:mysql://localhost/yongda?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #控制台输出sql日志 logging.level.com.springboot.springcache.mapper=debug spring.redis.host=#### spring.redis.port=#### spring.redis.password=#### |
SpringcacheApplication
package com.springboot.springcache; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; /** * 快速体验缓存 * 步骤: * 1.开启基于注解的缓存 @EnableCaching * 2.标注缓存注解 * @Cacheable * @CachePut * @CacheEvict * 整合redis缓存 * 1.安装redis服务器 * 2.引入redis的starter * 3.配置redis * 4.测试缓存 * 原理 CacheManager===Cache缓存组件来实际给缓存中存取数据 * 1.引入redis的starter,容器中存储的是RedisCacheManager * 2.RedisCacheManager帮我们创建 RedisCache 来作为缓存组件;RedisCache通过操作redis缓存数据 * 3.默认保存数据 k-v 都是Object,利用序列化保存,如何保存为json * ①引入了redis的starter,cacheManager变为RedisCacheManager * ②默认创建的 RedisCacheManager 操作redis 的时候,使用的是RedisTemplate<Object, Object> * ③RedisTemplate<Object, Object>是默认使用jdk的序列化机制 * 4.自定义CacheManager * springboot缓存默认的序列化是jdk提供的 Serializable 方式 */ @MapperScan(basePackages = {"com.springboot.springcache.mapper"}) @SpringBootApplication @EnableCaching public class SpringcacheApplication { public static void main(String[] args) { SpringApplication.run(SpringcacheApplication.class, args); } } |
src\main\java\com\springboot\springcache\config\BatisConfig.java
package com.springboot.springcache.config; import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class BatisConfig { @Bean public ConfigurationCustomizer configurationCustomizer(){ return new ConfigurationCustomizer() { @Override public void customize(org.apache.ibatis.session.Configuration configuration) { // 字段属性和javabean开启驼峰映射 configuration.setMapUnderscoreToCamelCase(true); } }; } } |
src\main\java\com\springboot\springcache\config\MyRedisConfig.java
package com.springboot.springcache.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.omg.CORBA.OBJECT_NOT_EXIST; import org.springframework.cache.CacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; @Configuration public class MyRedisConfig { @Bean public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){ RedisTemplate<Object,Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer<Object> ser = new Jackson2JsonRedisSerializer<Object>(Object.class); template.setDefaultSerializer(ser); return template; } @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { RedisSerializer<String> redisSerializer = new StringRedisSerializer(); 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); // 配置序列化(解决乱码的问题),过期时间30秒 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofSeconds(30)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) .disableCachingNullValues(); RedisCacheManager cacheManager = RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); return cacheManager; } } |
src\main\java\com\springboot\springcache\mapper\EmployeeMapper.java
package com.springboot.springcache.mapper; import com.springboot.springcache.bean.Employee; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; public interface EmployeeMapper { @Select("SELECT * FROM employee WHERE id = #{id}") public Employee getEmpById(Integer id); @Update("UPDATE employee SET last_name=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} WHERE id=#{id}") public void updateEmp(Employee employee); @Select("SELECT * FROM employee WHERE last_name=#{lastName}") public Employee getEmpByLastName(String lastName); } |
src\main\java\com\springboot\springcache\service\EmployeeService.java
package com.springboot.springcache.service; import com.springboot.springcache.bean.Employee; import com.springboot.springcache.mapper.EmployeeMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.*; import org.springframework.stereotype.Service; // 缓存的公共配置,其他的地方就可以省略 @CacheConfig(cacheNames="emp") @Service public class EmployeeService { @Autowired EmployeeMapper employeeMapper; /** * @Cacheable 将方法的运行结果进行缓存,以后再要相同的数据,直接从缓存中取,不用调用方法 * CacheManager管理多个Cache组件的,对缓存的真正CURD操作在Cache组件中,每一个缓存组件有自己的唯一一个名字 * 几个属性: * cacheNames/value 指定缓存的名字,将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存 * key:缓存数据使用的key,可以使用它来指定,默认是使用 方法 + 参数 的值 * cacheManager缓存管理器 或者 指定 cacheResolver缓存解析器 二选一 * condition:指定符合条件的情况下,才缓存 * unless 否定缓存 ;当unless指定的条件为true,方法的返回值就不会被缓存,可以获得结果进行判断 * sync: */ @Cacheable(condition = "#id >2") public Employee getEmpById(Integer id){ return employeeMapper.getEmpById(id); } /** * @CachePut 既调用方法,又更新缓存数据 * 修改了数据库的某个数据,同时更新缓存,使用CachePut注解,方法每次都会执行 * 运行时机 * 1.先调用目标方法,然后将目标方法的结果缓存起来 * * 测试步骤 * * 步骤1 查询3号员工 {"id":3,"lastName":"44444","email":"2222@","gender":1,"dId":1} * 步骤2 更新3号员工 {"id":3,"lastName":"hahaha","email":"444@qq.com","gender":1,"dId":1} * 步骤3 查询3号员工 继续从缓存中取出数据 因为默认情况下,key值不同, */ @CachePut(value={"emp"},key="#employee.id") public Employee updateEmp(Employee employee){ employeeMapper.updateEmp(employee); return employee; } /** * 清除缓存 * allEntries 清除所有缓存,默认为false * beforeInvocation 缓存清除操作是否在方法之前执行 默认为false(在方法之后执行) */ @CacheEvict(value={"emp"},/*key="#id",*/allEntries=true) public void deleteEmp(Integer id){ System.out.println("delete"+id); } @Caching( cacheable={ @Cacheable(value="emp",key="#lastName") }, put={ @CachePut(value="emp",key="#result.id"), @CachePut(value="emp",key="#result.email") } ) public Employee getEmpByLastName(String lastName){ return employeeMapper.getEmpByLastName(lastName); } } |
src\main\java\com\springboot\springcache\controller\EmployeeController.java
package com.springboot.springcache.controller; import com.springboot.springcache.bean.Employee; import com.springboot.springcache.service.EmployeeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class EmployeeController { @Autowired EmployeeService employeeService; @GetMapping("/emp/{id}") public Employee getEmployee(@PathVariable("id") Integer id){ Employee employee = employeeService.getEmpById(id); return employee; } @GetMapping("/emp") public Employee updateEmp(Employee employee) { employeeService.updateEmp(employee); return employee; } @GetMapping("getEmpByLastName/{lastName}") public Employee getEmpByLastName(@PathVariable("lastName") String lastName){ return employeeService.getEmpByLastName(lastName); } } |
RabbitMQ
透彻rabbitmq https://zhuanlan.zhihu.com/p/63700605
docker pull rabbitmq:3.6-management docker run -p 5672:5672 -p 15672:15672 --name myrabiitmq rabbitmq [root@VM_0_8_centos ~]# docker exec -it myrabiitmq sh # rabbitmq-plugins enable rabbitmq_management Enabling plugins on node rabbit@81a526814fd2: rabbitmq_management The following plugins have been configured: rabbitmq_management rabbitmq_management_agent rabbitmq_web_dispatch Applying plugin configuration to rabbit@81a526814fd2... The following plugins have been enabled: rabbitmq_management rabbitmq_management_agent rabbitmq_web_dispatch |
默认账号密码guest
无法访问rabbitmq管理后台问题 https://www.cnblogs.com/yuebo/p/11732769.html
application.properties文件
spring.rabbitmq.host=111.230.6.126 spring.rabbitmq.username=guest spring.rabbitmq.password=guest spring.rabbitmq.port=5672 |
RabbitmqtestApplication入口类
package com.springboot.rabbitmqtest; import org.springframework.amqp.rabbit.annotation.EnableRabbit; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * 1. RabbitAutoConfiguration * 2. 连接工厂 ConnectionFactory * 3. RabbitProperties封装了RabbitMq的配置 * 4. RabbitTemplate 操作Rabbitmq,给Rabbitmq发送和接收消息 * 5. AmqpAdmin 系统功能管理功能组件 */ @EnableRabbit @SpringBootApplication public class RabbitmqtestApplication { public static void main(String[] args) { SpringApplication.run(RabbitmqtestApplication.class, args); } } |
MyAMQPConfig json存取消息
package com.springboot.rabbitmqtest.config; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MyAMQPConfig { @Bean public MessageConverter messageConverter(){ return new Jackson2JsonMessageConverter(); } } |
BookRabbitService监听消息队列
package com.springboot.rabbitmqtest.service; import com.springboot.rabbitmqtest.bean.Book; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Service; @Service public class BookRabbitService { @RabbitListener(queues = "atguigu") public void receive(Book book){ System.out.println("收到一条消息"+book); } } |
单元测试
package com.springboot.rabbitmqtest; import com.springboot.rabbitmqtest.bean.Book; import org.junit.jupiter.api.Test; import org.springframework.amqp.core.*; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.HashMap; import java.util.Map; @SpringBootTest class RabbitmqtestApplicationTests { @Autowired RabbitTemplate rabbitTemplate; @Autowired AmqpAdmin amqpAdmin; @Test void contextLoads() { // Message需要自己构造一个,定义消息体内容和消息头 // rabbitTemplate.send( exchange ,rootKey , Message ); //Object默认当成消息体,只需要传入要发送的对象,自动序列化发送给rabbitmq // rabbitTemplate.convertAndSend( exchange ,rootKey , object ); // 默认序列化会产生乱码 Map<String,Object> map = new HashMap<>(); map.put("msg","这是第一条消息"); map.put("param","paraminfo"); rabbitTemplate.convertAndSend("exchange.direct","atguigu",map); } @Test void sendBook(){ rabbitTemplate.convertAndSend("exchange.direct","atguigu",new Book(1,"西游记")); } @Test void receiveMsg(){ Object object = rabbitTemplate.receiveAndConvert("atguigu"); //System.out.println(object.getClass()); System.out.println(object); } @Test void amqpAdminTest(){ // 创建队列 // Queue amqpAdminQuee = new Queue("amqpAdminQuee"); // amqpAdmin.declareQueue(amqpAdminQuee); // 创建交换器 //DirectExchange amqpexhange = new DirectExchange("amqpexhange"); //amqpAdmin.declareExchange(amqpexhange); amqpAdmin.declareBinding(new Binding("amqpAdminQuee", Binding.DestinationType.QUEUE,"amqpexhange","amqpkey",null)); } } |
SpringBoot使用Elasticsearch
PUT /megacorp/employee/1 raw JSON { "first_name" : "John", "last_name" : "Smith", "age" : 25, "about" : "I love to go rock climbing", "interests": [ "sports", "music" ] } { "_index": "megacorp", "_type": "employee", "_id": "1", "_version": 1, "result": "created", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "created": true } |
GET /megacorp/employee/1 { "_index": "megacorp", "_type": "employee", "_id": "1", "_version": 1, "found": true, "_source": { "first_name": "John", "last_name": "Smith", "age": 25, "about": "I love to go rock climbing", "interests": [ "sports", "music" ] } } |
HEAD /megacorp/employee/5
发出一个HEAD请求,如果存在这个数据,则响应200,如果不存在这个数据,则响应404
DELETE /megacorp/employee/4
GET /megacorp/employee/_search { "found": true, "_index": "megacorp", "_type": "employee", "_id": "4", "_version": 2, "result": "deleted", "_shards": { "total": 2, "successful": 1, "failed": 0 } } { "took": 26, "timed_out": false, "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 }, "hits": { "total": 3, "max_score": 1.0, "hits": [ { "_index": "megacorp", "_type": "employee", "_id": "2", "_score": 1.0, "_source": { "first_name": "Jane", "last_name": "Smith", "age": 32, "about": "I like to collect rock albums", "interests": [ "music" ] } }, { "_index": "megacorp", "_type": "employee", "_id": "1", "_score": 1.0, "_source": { "first_name": "John", "last_name": "Smith", "age": 25, "about": "I love to go rock climbing", "interests": [ "sports", "music" ] } }, { "_index": "megacorp", "_type": "employee", "_id": "3", "_score": 1.0, "_source": { "first_name": "Douglas", "last_name": "Fir", "age": 35, "about": "I like to build cabinets", "interests": [ "forestry" ] } } ] } } |
GET /megacorp/employee/_search?q=last_name:Smith { "took": 11, "timed_out": false, "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 }, "hits": { "total": 2, "max_score": 0.2876821, "hits": [ { "_index": "megacorp", "_type": "employee", "_id": "2", "_score": 0.2876821, "_source": { "first_name": "Jane", "last_name": "Smith", "age": 32, "about": "I like to collect rock albums", "interests": [ "music" ] } }, { "_index": "megacorp", "_type": "employee", "_id": "1", "_score": 0.2876821, "_source": { "first_name": "John", "last_name": "Smith", "age": 25, "about": "I love to go rock climbing", "interests": [ "sports", "music" ] } } ] } } |
GET /megacorp/employee/_search JSON数据 { "query" : { "match" : { "last_name" : "Smith" } } } { "took": 24, "timed_out": false, "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 }, "hits": { "total": 2, "max_score": 0.2876821, "hits": [ { "_index": "megacorp", "_type": "employee", "_id": "2", "_score": 0.2876821, "_source": { "first_name": "Jane", "last_name": "Smith", "age": 32, "about": "I like to collect rock albums", "interests": [ "music" ] } }, { "_index": "megacorp", "_type": "employee", "_id": "1", "_score": 0.2876821, "_source": { "first_name": "John", "last_name": "Smith", "age": 25, "about": "I love to go rock climbing", "interests": [ "sports", "music" ] } } ] } } |
复杂搜索
GET /megacorp/employee/_search json 数据 { "query" : { "bool": { "must": { "match" : { "last_name" : "smith" } }, "filter": { "range" : { "age" : { "gt" : 30 } } } } } } 响应 { "took": 57, "timed_out": false, "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 }, "hits": { "total": 1, "max_score": 0.2876821, "hits": [ { "_index": "megacorp", "_type": "employee", "_id": "2", "_score": 0.2876821, "_source": { "first_name": "Jane", "last_name": "Smith", "age": 32, "about": "I like to collect rock albums", "interests": [ "music" ] } } ] } } |
全文检索 /megacorp/employee/_search json数据 { "query" : { "match" : { "about" : "rock climbing" } } } 响应: { "took": 15, "timed_out": false, "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 }, "hits": { "total": 2, "max_score": 0.53484553, "hits": [ { "_index": "megacorp", "_type": "employee", "_id": "1", "_score": 0.53484553, "_source": { "first_name": "John", "last_name": "Smith", "age": 25, "about": "I love to go rock climbing", "interests": [ "sports", "music" ] } }, { "_index": "megacorp", "_type": "employee", "_id": "2", "_score": 0.26742277, "_source": { "first_name": "Jane", "last_name": "Smith", "age": 32, "about": "I like to collect rock albums", "interests": [ "music" ] } } ] } } |
短语搜索 GET /megacorp/employee/_search { "query" : { "match_phrase" : { "about" : "rock climbing" } } } 返回结果 { ... "hits": { "total": 1, "max_score": 0.23013961, "hits": [ { ... "_score": 0.23013961, "_source": { "first_name": "John", "last_name": "Smith", "age": 25, "about": "I love to go rock climbing", "interests": [ "sports", "music" ] } } ] } } |
高亮搜索 GET /megacorp/employee/_search { "query" : { "match_phrase" : { "about" : "rock climbing" } }, "highlight": { "fields" : { "about" : {} } } } { ... "hits": { "total": 1, "max_score": 0.23013961, "hits": [ { ... "_score": 0.23013961, "_source": { "first_name": "John", "last_name": "Smith", "age": 25, "about": "I love to go rock climbing", "interests": [ "sports", "music" ] }, "highlight": { "about": [ "I love to go <em>rock</em> <em>climbing</em>" ] } } ] } } |
Springboot整合springdata-elasticsearch操作Elasticsearch
springboot版本
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> <relativePath/> </parent> |
添加spring-boot-starter-data-elasticsearch依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> |
ElasticsearchApplication入口类
/** * 2. Springdata Elasticsearch * 1.ElasticSearchTemplate * 2.Client节点信息 clusterNodes、clusterName */ public static void main(String[] args) { SpringApplication.run(ElasticsearchApplication.class, args); } |
Book.java
package com.springboot.elasticsearch.bean; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; @Document(indexName = "bookindex",type = "bookindex") public class Book{ @Id private Integer id; private String bookName; private String author; public Book() { } public Book(Integer id, String bookName, String author) { this.id = id; this.bookName = bookName; this.author = author; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getBookName() { return bookName; } public void setBookName(String bookName) { this.bookName = bookName; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } @Override public String toString() { return "Book{" + "id=" + id + ", bookName='" + bookName + '\'' + ", author='" + author + '\'' + '}'; } } |
BookRespository.java
package com.springboot.elasticsearch.respository; import com.springboot.elasticsearch.bean.Book; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import java.util.List; public interface BookRespository extends ElasticsearchRepository<Book, Integer> { public List<Book> findByBookNameLike(String bookName); } |
测试类
package com.springboot.elasticsearch; import com.springboot.elasticsearch.bean.Book; import com.springboot.elasticsearch.respository.BookRespository; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; // RunWith(SpringRunner.class) 让测试在Spring容器环境下执行。如测试类中无此注解,将导致service,dao等自动注入失败 @RunWith(SpringRunner.class) @SpringBootTest public class ElasticsearchApplicationTests { @Autowired private BookRespository bookRespository; @Test public void contextLoads() { Book book = new Book(); book.setId(2); book.setAuthor("吴承恩"); book.setBookName("西游记"); // System.out.println(book); bookRespository.index(book); System.out.println(bookRespository.findByBookNameLike("游")); } } |
failed to load elasticsearch nodes : org.elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available: [{#transport#-1}{LEaUN9xGR6mUP5NQcUV-jw}{111.230.6.126}{111.230.6.126:9300}] |
springdata elastisearch 和 elastisearch版本适配关系
https://docs.spring.io/spring-data/elasticsearch/docs/3.2.0.RC3/reference/html/#preface.versions
docker运行elasticsearch镜像
docker run -e ES_JAVA_OPTS="-Xms250m -Xmx250m" -d -p 9201:9200 -p 9301:9300 --name ES02 elasticsearch:6.8.1 |
indexName值必须小写
@Document(indexName = "bookindex",type = "book") |
查看elasticsearch过往版本
https://www.elastic.co/cn/downloads/past-releases#elasticsearch
{"error":{"root_cause":[{"type":"index_not_found_exception","reason":"no such index","resource.type":"index_or_alias","resource.id":"bookindex","index_uuid":"_na_","index":"bookindex"}],"type":"index_not_found_exception","reason":"no such index","resource.type":"index_or_alias","resource.id":"bookindex","index_uuid":"_na_","index":"bookindex"},"status":404} |
ElasticsearchRepository.class
package org.springframework.data.elasticsearch.repository; import java.io.Serializable; import org.elasticsearch.index.query.QueryBuilder; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.elasticsearch.core.query.SearchQuery; import org.springframework.data.repository.NoRepositoryBean; @NoRepositoryBean public interface ElasticsearchRepository<T, ID extends Serializable> extends ElasticsearchCrudRepository<T, ID> { <S extends T> S index(S var1); Iterable<T> search(QueryBuilder var1); Page<T> search(QueryBuilder var1, Pageable var2); Page<T> search(SearchQuery var1); Page<T> searchSimilar(T var1, String[] var2, Pageable var3); void refresh(); Class<T> getEntityClass(); } |
Elasticsearch 6.0及以上版本 一个索引只允许有一个type
异步任务、定时任务、邮件任务
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> |
application.properties
spring.mail.host=smtp.qq.com spring.mail.username=412198579@qq.com spring.mail.password=ynwrralgwyyxbjhi spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true |
AsynctaskApplication.java 入口类
package com.springboot.asynctask; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; @EnableAsync // 开启基于注解的异步功能 @EnableScheduling // 开启基于注解的定时任务功能 @SpringBootApplication public class AsynctaskApplication { public static void main(String[] args) { SpringApplication.run(AsynctaskApplication.class, args); } } |
AsyncService 异步任务
package com.springboot.asynctask.service; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Service public class AsyncService { @Async // 声明这是一个异步方法 public void hello(){ try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("hello AsyncService"); } } |
定时任务
package com.springboot.asynctask.service; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @Service public class SchedulingService { @Scheduled(cron="1 * * * * *") // [秒] [分] [小时] [日] [月] [周] [年] public void hello(){ System.out.println("hello schedulingService"); } } |
异步任务测试
package com.springboot.asynctask.controller; import com.springboot.asynctask.service.AsyncService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @Autowired AsyncService asyncService; @GetMapping("/hello") public String hello() { asyncService.hello(); return "hello"; } } |
测试邮件发送
package com.springboot.asynctask; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import javax.mail.internet.MimeMessage; import java.io.File; @SpringBootTest class AsynctaskApplicationTests { @Autowired JavaMailSender javaMailSender; @Test void contextLoads() throws Exception { // 创建一个简单邮件 // SimpleMailMessage message = new SimpleMailMessage(); // message.setSubject("测试邮件"); // 邮件标题 // message.setText("邮件文本"); // 邮件内容 // message.setFrom("412198579@qq.com"); // message.setTo("412198579@qq.com"); // javaMailSender.send(message); // 创建一个复杂邮件 MimeMessage mimeMessage = javaMailSender.createMimeMessage(); MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true); mimeMessageHelper.setFrom("412198579@qq.com"); mimeMessageHelper.setTo("412198579@qq.com"); mimeMessageHelper.setSubject("subject"); mimeMessageHelper.setText("<b>hello</b>",true); mimeMessageHelper.addAttachment("1.jpg",new File("E:\\image\\1.jpg")); javaMailSender.send(mimeMessage); } } |
Spring-Security安全
引入Spring Security模块
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> </dependency> |
编写Spring Security配置
config\MySecurityConfig.java
package com.springboot.mysecurity.config; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @EnableWebSecurity public class MySecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception { // super.configure(http); //定制请求的授权规则 http.authorizeRequests().antMatchers("/").permitAll() .antMatchers("/level1/**").hasRole("VIP1") .antMatchers("/level2/**").hasRole("VIP2") .antMatchers("/level3/**").hasRole("VIP3"); // 开启自动配置的登录功能 http.formLogin().usernameParameter("username").passwordParameter("password").loginPage("/userlogin"); // ①/login来到登录页 // ②重定向到 /login?error表示登录失败 // ③更多详细 // ④默认post形式的/login代表处理登录 ,一旦定制loginPage,那么loginPage的post请求就是登录 // 开启自动配置的注销功能(访问logout清空session) 配置注销成功重定向的页面 http.logout().logoutSuccessUrl("/"); // 记录功能 关闭浏览器依旧可以访问 http.rememberMe(); } // 定义认证规则 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //super.configure(auth); auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP2") .and() .withUser("lisi").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP2","VIP3") .and() .withUser("wangwu").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP3"); } } |
KungfuController控制器
package com.springboot.mysecurity.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @Controller public class KungfuController { private final String PREFIX = "pages/"; /** * 欢迎页 * @return */ @GetMapping("/") public String index() { return "welcome"; } /** * 登陆页 * @return */ @GetMapping("/userlogin") public String loginPage() { return PREFIX+"login"; } /** * level1页面映射 * @param path * @return */ @GetMapping("/level1/{path}") public String level1(@PathVariable("path")String path) { return PREFIX+"level1/"+path; } /** * level2页面映射 * @param path * @return */ @GetMapping("/level2/{path}") public String level2(@PathVariable("path")String path) { return PREFIX+"level2/"+path; } /** * level3页面映射 * @param path * @return */ @GetMapping("/level3/{path}") public String level3(@PathVariable("path")String path) { return PREFIX+"level3/"+path; } } |
login.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <h1 align="center">欢迎登陆武林秘籍管理系统</h1> <hr> <div align="center"> <form th:action="@{/userlogin}" method="post"> 用户名:<input name="username"/><br> 密码:<input name="password"><br/> <input type="checkbox" name="remeber"> 记住我<br/> <input type="submit" value="登陆"> </form> </div> </body> </html> |
welcome.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1 align="center">欢迎光临武林秘籍管理系统</h1> <div sec:authorize="!isAuthenticated()"> <h2 align="center">游客您好,如果想查看武林秘籍 <a th:href="@{/userlogin}">请登录</a></h2> </div> <div sec:authorize="isAuthenticated()"> <h2><span sec:authentication="name"></span>,您好,您的角色有: <span sec:authentication="principal.authorities"></span></h2> <form th:action="@{/logout}" method="post"> <input type="submit" value="注销"/> </form> </div> <hr> <div sec:authorize="hasRole('VIP1')"> <h3>普通武功秘籍</h3> <ul> <li><a th:href="@{/level1/1}">罗汉拳</a></li> <li><a th:href="@{/level1/2}">武当长拳</a></li> <li><a th:href="@{/level1/3}">全真剑法</a></li> </ul> </div> <div sec:authorize="hasRole('VIP2')"> <h3>高级武功秘籍</h3> <ul> <li><a th:href="@{/level2/1}">太极拳</a></li> <li><a th:href="@{/level2/2}">七伤拳</a></li> <li><a th:href="@{/level2/3}">梯云纵</a></li> </ul> </div> <div sec:authorize="hasRole('VIP3')"> <h3>绝世武功秘籍</h3> <ul> <li><a th:href="@{/level3/1}">葵花宝典</a></li> <li><a th:href="@{/level3/2}">龟派气功</a></li> <li><a th:href="@{/level3/3}">独孤九剑</a></li> </ul> </div> </body> </html> |
devtools开发热部署
1.引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> |
2.快捷键ctrl+f9
actuator监管端点测试
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> |
应用程序启动,控制台显示:
Exposing 2 endpoint(s) beneath base path ‘/actuator’,默认只开启了health,info两个端点,访问http://localhost:8080/actuator响应如下:
{ _links: { self: { href: "http://localhost:8080/actuator", templated: false }, health: { href: "http://localhost:8080/actuator/health", templated: false }, health-path: { href: "http://localhost:8080/actuator/health/{*path}", templated: true }, info: { href: "http://localhost:8080/actuator/info", templated: false } } } |
application.properties
# 开启所有端点 management: endpoints: # 这里是 endpoints web: # 默认路径 base-path: /actuator exposure: # Endpoint IDs that should be included or '*' for all. include: '*' # 显示详细的 health 信息 jmx: # Whether unique runtime object names should be ensured. domain: org.springframework.boot exposure: # Endpoint IDs that should be included or '*' for all. include: '*' # 显示详细的 health 信息 endpoint: # 这里是 endpoint health: show-details: always # 打开 shutdown 端点,通过 POST 访问该端点可以关闭应用 shutdown: enabled: true |