SpringBoot初级踩坑

SpringBoot 笔记(雷神)
掺杂了一些相关知识
SpringBoot:
是一个便捷搭建基于Spring工程的脚手架,作用是帮助开发人员搭建大型的Spring项目。简化工程的配置,依赖管理;实现开发人员把时间集中在业务开发上。

优点:
– 快速创建独立运行的Spring项目以及与主流框架集成
– 使用嵌入式的Servlet容器,应用无需打成WAR包
– starters自动依赖与版本控制
– 大量的自动配置,简化开发,也可修改默认值
– 无需配置XML,无代码生成,开箱即用
– 准生产环境的运行时应用监控
– 与云计算的天然集成

spring-boot-starter-web:
spring-boot-starter:spring-boot场景启动器;帮我们导入了web模块正常运行所依赖的组件;
Spring Boot将所有的功能场景都抽取出来,做成一个个的starters(启动器),只需要在项目里面引入这些starter 相关场景的所有依赖都会导入进来。要用什么功能就导入什么场景的启动器

/** * @SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用
*/ @SpringBootApplication
public class HelloWorldMainApplication
{
public static void main(String[] args)
{ // Spring应用启动起来
SpringApplication.run(HelloWorldMainApplication.class,args);
}
}

@SpringBootApplication:
Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot 就应该运行这个类的main方法来启动SpringBoot应用;
@EnableAutoConfiguration:开启自动配置功能;
以前我们需要配置的东西,Spring Boot帮我们自动配置;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能;这样自动配置才能生效;

@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

@AutoConfigurationPackage:自动配置包
@Import(AutoConfigurationPackages.Registrar.class):
Spring的底层注解@Import,给容器中导入一个组件;导入的组件由 AutoConfigurationPackages.Registrar.class;
将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器;

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

会给容器中导入非常多的自动配置类(xxxAutoConfiguration);就是给容器中导入这个场景需要的所有组件, 并配置好这些组件;
有了自动配置类,免去了我们手动编写配置注入功能组件等的工作;
原理:
通过这个SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,classLoader)方法;
Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将 这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作;
以前我们需要自己配置的东西,自动配置类都帮我们;

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

默认生成的Spring Boot项目;
主程序已经生成好了,我们只需要我们自己的逻辑 resources文件夹中目录结构

static:保存所有的静态资源; js css images;

templates:保存所有的模板页面;(Spring Boot默认jar包使用嵌入式的Tomcat,默认不支持JSP页 面);可以使用模板引擎(freemarker、thymeleaf);
application.properties:Spring Boot应用的配置文件;可以修改一些默认设置;

yml语法:。。。
调用配置文件内容注入
@Value获取值和@ConfigurationProperties获取值比较

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

如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value; 如果说,我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties

4、@PropertySource&@ImportResource&@Bean

@PropertySource:加载指定的配置文件
@PropertySource(value = {“classpath:person.properties”})//加载指定的配置文件,就不用把所有配置信息全放在全局配置文件下面

1.@ImportResource:导入Spring的配置文件,让配置文件里面的内容生效; Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别; 想让Spring的配置文件生效,加载进来;@ImportResource标注在一个配置类上(也可以写在主配置类上,也可以是专门写的一个配置类)

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

2.SpringBoot推荐给容器中添加组件的方式而不是上面@ImportResource;
推荐使用全注解的方式
1、配置类@Configuration------>Spring配置文件
2、使用@Bean给容器中添加组件
**

  • 在配置文件中写标签添加组件
    */
    @Configuration //指明当前类是一个配置类:就是来替代spring配置文件(.xml)
    public class MyAppConfig {

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

5.profile
1、多Profile文件
我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml
默认使用application.properties的配置;

3、激活指定profile
1、在配置文件中指定 spring.profiles.active=dev

#在yml文件中做类似于properties文件中profile文件的工作
#yml文档块模式

server:
port:8081
#激活dev(激活指定输入哪个环境)
spring:
profiles:
active: dev

server:
port: 8083
spring:
profiles: dev

server:
port: 8084
spring:
profiles: prod
2、命令行:
生成打包的jar文件,在该文件目录下输入命令行
java -jar spring-boot-02-config-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev;
可以直接在测试的时候,配置传入命令行参数
3、虚拟机参数;
-Dspring.profiles.active=dev

6、配置文件加载位置
springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文 件
–file:./config/
–file:./
–classpath:/config/
–classpath:/
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
SpringBoot会从这四个位置全部加载主配置文件;互补配置;

我们还可以通过spring.config.location来改变默认的配置文件位置
项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;指定配置文件和默 认加载的这些配置文件共同起作用形成互补配置;
java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --spring.config.location=G:/application.properties
这样在实际开发中如果需要新的配置文件,就不需要重新打包文件,直接在命令行窗口下输入命令
就可在新的配置文件下执行。

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

1.命令行参数
所有的配置都可以在命令行上进行指定
java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --server.port=8087 --server.context-path=/abc 多个配置用空格分开; --配置项=值

(2,3,4,5用的少)
2.来自java:comp/env的JNDI属性
3.Java系统属性(System.getProperties())
4.操作系统环境变量
5.RandomValuePropertySource配置的random.*属性值

由jar包外向jar包内进行寻找(项目打包完成的jar包);
优先加载带profile
6.jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件
7.jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件
再来加载不带profile
8.jar包外部的application.properties或application.yml(不带spring.profile)配置文件
9.jar包内部的application.properties或application.yml(不带spring.profile)配置文件
10.@Configuration注解类上的@PropertySource
11.通过SpringApplication.setDefaultProperties指定的默认属性

1、自动配置原理(spring boot1.xx 和2.xx内容不相同):
1)、SpringBoot启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfiguration
2)、@EnableAutoConfiguration 作用: 利用EnableAutoConfigurationImportSelector给容器中导入一些组件?
可以查看selectImports()方法的内容;
List configurations = getCandidateConfigurations(annotationMetadata, attributes);获取候选的配置
此getCandidateConfigurations方法下
loadFactoryNames 此方法下
SpringFactoriesLoader.loadFactoryNames
此方法下的
loadSpringFactories()方法、
{
Enumeration urls = classLoader != null ? classLoader.getResources(“META-INF/spring.factories”) : ClassLoader.getSystemResources(“META-INF/spring.factories”);
}
扫描所有jar包类路径下 META‐INF/spring.factories 把扫描到的这些文件的内容包装成properties对象
从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加在容器 中

总之:将 类路径下 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中;
类似如下:每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置;
每一个自动配置类进行自动配置功能;

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\ org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\ org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\ org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\ org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration, \ org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration ,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration ,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration
,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfi guration,\ org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\ org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\ org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\ org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\ org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\ org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\ org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\ org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\ org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\ org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\ org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\ org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\ org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\ org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\ org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\ org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration, \ org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\

org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\ org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\ org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\ org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\ org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\ org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\ org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\ org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\ org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\ org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\ org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\ org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\ org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\ org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\ org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\ org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\ org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\ org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\ org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\ org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

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

@Configuration //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@EnableConfigurationProperties(HttpEncodingProperties.class) //启动指定类的
ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把 HttpEncodingProperties加入到ioc容器中
@ConditionalOnWebApplication //Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果 满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是web应用,如果是,当前配置类生效
Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean。
@ConditionalOnClass(CharacterEncodingFilter.class) //判断当前项目有没有这个类 CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器; @ConditionalOnProperty(prefix = “spring.http.encoding”, value = “enabled”, matchIfMissing = true) //判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的 //machIfMissing=true即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
public class HttpEncodingAutoConfiguration {
//他已经和SpringBoot的配置文件映射了
private final HttpEncodingProperties properties;
//只有一个有参构造器的情况下,参数的值就会从ioc容器中拿
public HttpEncodingAutoConfiguration(HttpEncodingProperties properties)
{
this.properties = properties;
}
@Bean //给容器中添加一个组件,这个组件的某些值需要从properties中获取
@ConditionalOnMissingBean(CharacterEncodingFilter.class) //判断容器没有这个组件?
public CharacterEncodingFilter characterEncodingFilter()
{
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
总结:
根据当前不同的条件判断,决定这个配置类是否生效?
一但这个配置类生效:这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;

精髓:
1)SpringBoot启动会加载大量的自动配置类
2)我们看我们需要的功能有没有SpringBoot默认写好的自动配置类;
3)我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)
4)给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这 些属性的值;

xxxxAutoConfigurartion:自动配置类;
作用:给容器中添加组件
xxxxProperties:封装配置文件中相关属性;

自动配置类必须在一定的条件下才能生效;
我们怎么知道哪些自动配置类生效;
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置 类生效;

META-INF文件夹下的
Positive matches:(自动配置类启用的)
Negative matches:(没有启动,没有匹配成功的自动配置类)


日志:
SpringBoot:底层是Spring框架,Spring框架默认是用JCL;‘
SpringBoot选用 SLF4j和logback;

slf4j是一个门面(抽象层),logback是其中的一种实现

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

2、遗留问题
a(slf4j+logback): Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx
统一日志记录,即使是别的框架和我一起统一使用slf4j进行输出?

如何让系统中所有的日志都统一到slf4j;
1、将系统中其他日志框架先排除出去;
2、用中间包来替换原有的日志框架;
3、我们导入slf4j其他的实现

SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候,只需要 把这个框架依赖的日志框架排除掉即可;
//记录器
Logger logger = LoggerFactory.getLogger(getClass());
@Test
public void contextLoads() {
//System.out.println();
//日志的级别;
//由低到高
trace //可以调整输出的日志级别;
日志就只会在这个级别以以后的高级别生效
logger.trace(“这是trace日志…”);
logger.debug(“这是debug日志…”);
//SpringBoot默认给我们使用的是info级别的,没有指定级别的就用SpringBoot默认规定的级别;
root 级别
logger.info(“这是info日志…”);
logger.warn(“这是warn日志…”);
logger.error(“这是error日志…”);
}
日志输出格式: %d表示日期时间,
%thread表示线程名,
%‐5level:级别从左显示5个字符宽度
%logger{50} 表示logger名字长50个字符,否则按照句点分割。
%msg:日志消息,
%n是换行符
‐‐> %d{yyyy‐MM‐dd HH:mm:ss.SSS} [%thread] %‐5level %logger{50} ‐ %msg%n

#设置日志级别
logging.level.com.itguigu=trace

#不指定路径当前项目下生成springboot.log日志文件
#logging.file.name=springboot.log
#可以指定完整的路径
#logging.file.name=G:/springboot.log

#当前磁盘的根路径下创建Spring文件夹和里面的log文件夹:使用Spring.log作为默认文件,即生成在E盘根目录下的
logging.file.path=/spring/log

#在控制台输出的日志的格式
logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n
#在指定文件中日志输出的格式
logging.pattern.file=%d{yyyy-MM-dd}=[%thread]= %-5level === %logger{50}=== %msg%n

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

logback.xml:直接就被日志框架识别了;

logback-spring.xml(推荐使用这种):日志框架就不直接加载日志的配置项,由SpringBoot解析日志配置,可以使用SpringBoot 的高级Profile功能
例如:


可以指定某段配置只在某个环境下生效

#spring.profiles.active=dev

指的是开发环境下使用此配置 %d{yyyy‐MM‐dd HH:mm:ss.SSS} ‐‐‐‐> [%thread] ‐‐‐> %‐5level %logger{50} ‐ %msg%n 不在开发环境下使用此配置 %d{yyyy‐MM‐dd HH:mm:ss.SSS} ==== [%thread] ==== %‐5level %logger{50} ‐ %msg%n

切换日志框架
先排除原先框架的依赖(会有原先框架的依赖和可能 存在的中间jar包的依赖,都需要排除),在添加新的自己使用的框架依赖

Web开发

使用SpringBoot;
1)、创建SpringBoot应用,选中我们需要的模块; 2)、SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来 3)、自己编写业务代码;

自动配置原理?
这个场景SpringBoot帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?xxx
xxxxAutoConfiguration:帮我们给容器中自动配置组件;
xxxxProperties:配置类来封装配置文件的内容

SpringBoot对静态资源的映射规则
1)、所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找资源;
webjars:以jar包的方式引入静态资源;
localhost:8080/webjars/jquery/3.3.1/dist/jquery.js


在访问的时候只需要写webjars下面资源的名称即可

org.webjars
jquery
3.3.1

“/**” 访问当前项目的任何资源,都去(静态资源的文件夹)找映射
静态资源文件夹下有以下:
“classpath:/META‐INF/resources/”,
“classpath:/resources/”,
“classpath:/static/”,
“classpath:/public/”
“/”:当前项目的根路径

3)、欢迎页; 静态资源文件夹下的所有index.html页面;被"/**"映射;
网址直接输入localhost:8080/,默认找index页面

4)配置喜欢的图标
所有的 **/favicon.ico 都是在静态资源文件下找;

3、模板引擎
JSP、Velocity、Freemarker、Thymeleaf
由于springboot采用的是jar包的方式,用的也是嵌入式的Tomcat,不支持jsp,
纯静态的页面开发太麻烦,
SpringBoot推荐的Thymeleaf模板引擎;
语法更简单,功能更强大;

Thymeleaf是用来开发Web和独立环境项目的服务器端的Java模版引擎
Spring官方支持的服务的渲染模板中,并不包含jsp。而是Thymeleaf和Freemarker等,而Thymeleaf与SpringMVC的视图技术,及SpringBoot的自动化配置集成非常完美,几乎没有任何成本,你只用关注Thymeleaf的语法即可。

Thymeleaf官方文档
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#expressions-on-selections-asterisk-syntax

Simple expressions:(表达式语法)
Variable Expressions: ${…}:获取变量值;OGNL;
1)、获取对象的属性、调用方法
2)、使用内置的基本对象:
#ctx : the context object.

Selection Variable Expressions: *{…}:
选择表达式:和 在 功 能 上 是 一 样 ; 补 充 : 配 合 t h : o b j e c t = " {}在功能上是一样; 补充:配合 th:object=" th:object="{session.user}:

Name: Sebastian.

Surname: Pepper.

Nationality: Saturn.



 [[${user}]] 

4、SpringMVC自动配置

2、扩展SpringMVC

编写一个配置类(@Configuration),是WebMvcConfigurerAdapter类型;不能标注@EnableWebMvc;
既保留了所有的自动配置,也能用我们扩展的配置;

//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
package springbootwebrestfulcrud.demo.config;

import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import static org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.*;

//使用WebMvcAutoConfigurationAdapter扩展SpringMvc的功能
//新版本被WebMvcConfigurer接口替换
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {

    //浏览器发送atguigu到success页面
    registry.addViewController("/atguigu").setViewName("success");//用什么方法跳转到什么页面
}

}

原理:
1)、WebMvcAutoConfiguration是SpringMVC的自动配置类
2)、在做其他自动配置时会导入;@Import(EnableWebMvcConfiguration.class)

@Configuration public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); //从容器中获取所有的WebMvcConfigurer
@Autowired(required = false)
public void setConfigurers(List configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
//一个参考实现;将所有的WebMvcConfigurer相关配置都来一起调用;
@Override
// public void addViewControllers(ViewControllerRegistry registry) {
// for (WebMvcConfigurer delegate : this.delegates) {
// delegate.addViewControllers(registry);
// }
}
}
}

3)、容器中所有的WebMvcConfigurer都会一起起作用;
4)、我们的配置类也会被调用;

效果:SpringMVC的自动配置和我们的扩展配置都会起作用;

3、全面接管SpringMVC;
SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己配置;所有的SpringMVC的自动配置都失效了 我们需要在配置类中添加@EnableWebMvc即可;
//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
@EnableWebMvc
@Configuration public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// super.addViewControllers(registry);
//浏览器发送 /atguigu 请求来到 success
registry.addViewController("/atguigu").setViewName(“success”);
}
}

原理:
为什么@EnableWebMvc自动配置就失效了;
1)@EnableWebMvc的核心
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {

@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

WebMvcAutoConfiguration.class

@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) //容器中没有这个组件的时候,这个自动配置类才生效,// 而自己写的配置类添加了EnableWebMvc
//最终添加了WebMvcConfigurationSupport,所以WebMvcConfigurationSupport的基本配置都没生效
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

4)、@EnableWebMvc将WebMvcConfigurationSupport组件导入进来;
5)、导入的WebMvcConfigurationSupport只是SpringMVC基本的功能;

2)、国际化
1)、编写国际化配置文件;
2)、使用ResourceBundleMessageSource管理国际化资源文件
3)、在页面使用fmt:message取出国际化内容

步骤:
1)、编写国际化配置文件,抽取页面需要显示的国际化消息
2)、SpringBoot自动配置好了管理国际化资源文件的组件;

@ConfigurationProperties(prefix = “spring.messages”) public class MessageSourceAutoConfiguration
{
/** * Comma‐separated list of basenames (essentially a fully‐qualified classpath
* location), each following the ResourceBundle convention with relaxed support for

  • slash based locations. If it doesn’t contain a package qualifier (such as
  • “org.mypackage”), it will be resolved from the classpath root.
    */ private String basename = “messages”;

//我们的配置文件可以直接放在类路径下叫messages.properties; 但是我们不是,所有需要在配置文件中设置文件路径 spring.messages.basename=

@Bean public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(this.basename)) {
//设置国际化资源文件的基础名(去掉语言国家代码的)
messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(this.basename)));
}
if (this.encoding != null) {
messageSource.setDefaultEncoding(this.encoding.name());
}
messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale);
messageSource.setCacheSeconds(this.cacheSeconds);
messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat);
return messageSource;

3)、去页面获取国际化的值;

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

Signin Template for Bootstrap

	


效果:根据浏览器语言设置的信息切换了国际化;
原理:
国际化Locale(区域信息对象);LocaleResolver(获取区域信息对象);

@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = “spring.mvc”, name = “locale”)
public LocaleResolver localeResolver() {
if (this.mvcProperties
.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
默认的就是根据请求头带来的区域信息获取Locale进行国际化

4)、点击链接切换国际化
package springbootwebrestfulcrud.demo.component;

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

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

public class MyLocaleResolver implements LocaleResolver {

//区域信息解析器,写完需要添加在容器中才生效,在myMvcConfig中添加到配置中 Bean
@Override
public Locale resolveLocale(HttpServletRequest httpServletRequest) {
//解析区域信息
String l = httpServletRequest.getParameter(“l”);//从httpServletRequest获取参数的值即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, HttpServletResponse httpServletResponse, Locale locale) {

}

}

Sign in

© 2017-2018

中文 此处用index.html要注意,切换国际化的时候url地址会一起改变 English

3)、登陆

开发期间模板引擎页面修改以后,要实时生效
1)、禁用模板引擎的缓存

禁用缓存

spring.thymeleaf.cache=false

2)、页面修改完成以后ctrl+f9:重新编译;

Please sign in

Username Password
[[#{login.Remember}]]
Sign in

© 2017-2018

中文 English

为了防止重复提交表单,不用上面这种写法,通过重定向的方式返回页面,避免重复提交
@PostMapping(value = “/user/login”)
// @RequestMapping(value = “/user/login”,method = RequestMethod.POST)
public String login(@RequestParam(“username”)String username,
@RequestParam String password, Map map){//从请求参数上获得username和password,
// 用RequestParam标注的属性一旦没有提交就报错
if(!StringUtils.isEmpty(username) && “123456”.equals(password)){
//登录成功
//return “dashboard”;
//为了防止重复提交表单,不用上面这种写法,通过重定向的方式返回页面,避免重复提交
return “redirect:/main.html”;//再配置文件中添加视图映射,转到dashboard.html
}else {
//登录失败
map.put(“msg”,“用户名密码错误”);
return “login”;
}

由于使用了重定向,直接输入url:http://localhost:8080/crud/main.html 就可直接访问到页面,跳过了登录检测的步骤,这不合理,需要添加拦截器进行登录检测
4)、拦截器进行登陆检查
拦截器

thymeleaf公共页面元素抽取
thymeleaf公共页面元素抽取

1、抽取公共片段


© 2011 The Good Thymes Virtual Grocery

2、引入公共片段
~{templatename::selector}:模板名::选择器 ~{templatename::fragmentname}:模板名::片段名 3、默认效果: insert的公共片段在div标签中 如果使用th:insert等属性进行引入,可以不用写~{}: 行内写法可以加上:[[~{}]];[(~{})];

三种引入公共片段的th属性:
th:insert:将公共片段整个插入到声明引入的元素中
th:replace:将声明引入的元素替换为公共片段
th:include:将被引入的片段的内容包含进这个标签中

© 2011 The Good Thymes Virtual Grocery

引入方式

效果

© 2011 The Good Thymes Virtual Grocery
© 2011 The Good Thymes Virtual Grocery
© 2011 The Good Thymes Virtual Grocery

第二种:通过选择器的方式

高亮设置:选择哪个按钮哪个高亮

7。错误处理机制

默认效果:
1)、浏览器,返回一个默认的错误页面

2)、如果是其他客户端,默认响应一个json数据
浏览器和客户端发送请求的请求头中的accept不一样
原理:
可以参照ErrorMvcAutoConfiguration;错误处理的自动配置;

步骤:
一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error 请求;就会被BasicErrorController处理;

1)响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的;

1、DefaultErrorAttributes:
帮我们在页面共享信息;Model的值就是调用DefaultErrorAttributes中的方法
 即页面能获取的信息就是通过DefaultErrorAttributes来显示的;
      timestamp:时间戳
      status:状态码
      error:错误提示
      exception:异常对象
     message:异常消息
     errors:JSR303数据校验的错误都在这里
@Override
	public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
			boolean includeStackTrace) {
		Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
		errorAttributes.put("timestamp", new Date());
		addStatus(errorAttributes, requestAttributes);
		addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
		addPath(errorAttributes, requestAttributes);
		return errorAttributes;
	}

​ 2、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 = getStatus(request);
		Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
				request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
        
        //去哪个页面作为错误页面;包含页面地址和页面内容
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
	}

	@RequestMapping
	@ResponseBody    //产生json数据,其他客户端来到这个方法处理;
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		Map<String, Object> body = getErrorAttributes(request,
				isIncludeStackTrace(request, MediaType.ALL));
		HttpStatus status = getStatus(request);
		return new ResponseEntity<Map<String, Object>>(body, status);
	}

​ 3、ErrorPageCustomizer:

	@Value("${error.path:/error}")
	private String path = "/error";  系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则)

​ 4、DefaultErrorViewResolver:

@Override
	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
			Map<String, Object> model) {
		ModelAndView modelAndView = resolve(String.valueOf(status), model);
		if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
			modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
		}
		return modelAndView;
	}

	private ModelAndView resolve(String viewName, Map<String, Object> model) {
        //默认SpringBoot可以去找到一个页面?  error/404
		String errorViewName = "error/" + viewName;
        
        //模板引擎可以解析这个页面地址就用模板引擎解析
		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
				.getProvider(errorViewName, this.applicationContext);
		if (provider != null) {
            //模板引擎可用的情况下返回到errorViewName指定的视图地址
			return new ModelAndView(errorViewName, model);
		}
        //模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面   error/404.html
		return resolveResource(errorViewName, model);
	}


步骤:

​一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error请求;就会被**BasicErrorController**处理;

​1)响应页面;拿到状态码,拿到一些Model数据,返回一个ModelAndView(即要去的页面,怎么得到的,是使用resolveErrorView方法),去哪个页面是由**DefaultErrorViewResolver**解析得到的;

```java
protected ModelAndView resolveErrorView(HttpServletRequest request,
      HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
    //所有的ErrorViewResolver(异常视图解析器)得到ModelAndView
   for (ErrorViewResolver resolver : this.errorViewResolvers) {
      ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
      if (modelAndView != null) {
         return modelAndView;
      }
   }
   return null;
}

2)、如果定制错误响应:
1)、如何定制错误的页面;

1)、有模板引擎的情况下;error/状态码; 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;即在resources下面的temeplates下新建一个error文件夹,里面放置404.html等一些其他页面

我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态 码.html);
页面能获取的信息;
timestamp:时间戳

status:状态码
error:错误提示

exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里

即可在自定义错误页面4xx.html或者404.html中显示自己想要的信息

status:[[${status}]]

timestamp:[[${timestamp}]]

2)、没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找; 3)、以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;

有模板引擎先通过BasicErrorController的errorviewResolver(异常视图解析器)找template下的error文件夹下的页面显示,没有即返回null,返回null就默认转发到error视图,error视图在ErrorMvcAutoConfiguration 下面,容器中有一个视图对象叫error,
@Bean(“error”),返回defaultErrorView

2)、如何定制错误的json数据;

1)、自定义异常处理&返回定制json数据;

//浏览器客户端返回的都是json请求,没有自适应效果
@ControllerAdvice //异常处理器标识
public class MyExceptionHandler {

@ResponseBody
@ExceptionHandler(UserNotExistException.class) //运用SpringMvc的ExceptionHandler捕获到UserNotExistException异常后,
                                                // 通过ResponseBody将数据显示出来
public  Map handle(Exception e){//把异常对象传进来

    Map map=new HashMap<>(); //响应自己的json数据,把内容传到Map中
    map.put("code","user.notexist");
    map.put("message",e.getMessage());
    return map;   //页面返回自己定制的json数据
}

}

转发到/error进行自适应响应效果处理
@ExceptionHandler(UserNotExistException.class) //运用SpringMvc的ExceptionHandler捕获到UserNotExistException异常后,
// 通过ResponseBody将数据显示出来
public String handle(Exception e, HttpServletRequest request){//把异常对象传进来

    Map map=new HashMap<>(); //响应自己的json数据,把内容传到Map中
     //传入我们自己的错误状态码  4xx 5xx,否则就不会进入定制错误页面的解析流程
    //需要传入我们自己的错误状态码 4xx.5xx

// Integer statusCode = (Integer)request.getAttribute(“javax.servlet.error.status_code”); springmvc获取状态码源码
request.setAttribute(“javax.servlet.error.status_code”,500);
map.put(“code”,“user.notexist”);
map.put(“message”,e.getMessage());
return “forward:error”; //转发到error是因为底层处理错误页面BasiErrorController是通过处理/error请求,而/error请求是自适应的
// 所以我们就把自定义的异常处理转给/error处理,让它来判断是客户端请求还是浏览器请求,判断完成后
// 然后会转发到我们自定义的错误页面5xx.html,会返回自己定制的json数据

3)将我们的定制数据携带出去(上一种方法客户端访问时返回的错误数据code和exception并没有显示我们自定义的数据,改用此方法);
出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由 BasicErrorController.class中的
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response)
方法中
getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法);
1、完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;(太麻烦)

2、页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;
那么就自定义一个MyErrorAttribute设置显示自己想要的数据

//给容器中加入我们自定义的ErrorAttribute
@Component
public class MyErrorAttribute extends DefaultErrorAttributes {
@Override
public Map getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {

    Map errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
    errorAttributes.put("company","guigu");
    return errorAttributes;

// return super.getErrorAttributes(webRequest, includeStackTrace);
}
}

最终的效果:响应是自适应的(即判定是客户端请求还是浏览器请求),可以通过定制ErrorAttributes改变需要返回的内容,

package springbootwebrestfulcrud.demo.controller;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import springbootwebrestfulcrud.demo.exception.UserNotExistException;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@ControllerAdvice //异常处理器标识
public class MyExceptionHandler {

// //浏览器客户端返回的都是json请求
// @ResponseBody
// @ExceptionHandler(UserNotExistException.class) //运用SpringMvc的ExceptionHandler捕获到UserNotExistException异常后,
// // 通过ResponseBody将数据显示出来
// public Map handle(Exception e){//把异常对象传进来
//
// Map map=new HashMap<>(); //响应自己的json数据,把内容传到Map中
// map.put(“code”,“user.notexist”);
// map.put(“message”,e.getMessage());
// return map; //页面返回自己定制的json数据
// }

@ExceptionHandler(UserNotExistException.class) //运用SpringMvc的ExceptionHandler捕获到UserNotExistException异常后,
                                                // 通过ResponseBody将数据显示出来
public  String handle(Exception e, HttpServletRequest request){//把异常对象传进来

    Map map=new HashMap<>(); //响应自己的json数据,把内容传到Map中

    //需要传入我们自己的错误状态码 4xx.5xx,否则就不会进入定制错误页面的解析流程

// Integer statusCode = (Integer)request.getAttribute(“javax.servlet.error.status_code”); springmvc BasicErrorController获取状态码源码
request.setAttribute(“javax.servlet.error.status_code”,500);
map.put(“code”,“user.notexist”);
map.put(“message”,“用户出错了”);
request.setAttribute(“ext”,map);//数据放在请求域中
return “forward:error”; //转发到error是因为底层处理错误页面BasiErrorController是通过处理/error请求,而/error请求是自适应的
// 所以我们就把自定义的异常处理转给/error处理,让它来判断是客户端请求还是浏览器请求,判断完成后
// 然后会转发到我们自定义的错误页面5xx.html,会返回自己定制的json数据

}

}

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

//给容器中加入我们自定义的ErrorAttribute
@Component
public class MyErrorAttribute extends DefaultErrorAttributes {

//返回值的map就是页面和json能获取的所有字段
@Override
public Map getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {

    Map errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
    errorAttributes.put("company","guigu");

    //我们自定义的异常处理器MyExceptionHandler携带的数据
    Map map = (Map) webRequest.getAttribute("ext", 0);//从请求域中获取自定义额外添加的信息,在MyExceptionHandler.java中
    errorAttributes.put("ext",map);
    return errorAttributes;

// return super.getErrorAttributes(webRequest, includeStackTrace);
}
}

5xx.html

status:[[${status}]]

timestamp:[[${timestamp}]]

message:[[${message}]]

exception:[[${exception}]]

ext:[[${ext.code}]]

exception:[[${ext.message}]]

			

8、配置嵌入式Servlet容器
SpringBoot默认使用Tomcat作为嵌入式的Servlet容器
1)、如何定制和修改Servlet容器的相关配置;
1、修改和server有关的配置(ServerProperties【也是EmbeddedServletContainerCustomizer】);
server.port=8081
server.context‐path=/crud
server.tomcat.uri‐encoding=UTF‐8
//通用的Servlet容器设置
server.xxx
//Tomcat的设置
server.tomcat.xxx

2、编写一个EmbeddedServletContainerCustomizer:嵌入式的Servlet容器的定制器;来修改Servlet容器的 配置
Spring Boot2.0以上版本EmbeddedServletContainerCustomizer被WebServerFactoryCustomizer替代

@Bean
public WebServerFactoryCustomizer webServerFactoryCustomizer(){
return new WebServerFactoryCustomizer(){
@Override
public void customize(ConfigurableWebServerFactory factory) {
factory.setPort(8083);
}
};
}

老版本
@Bean
//一定要将这个定制器加入到容器中
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer()
{
return new EmbeddedServletContainerCustomizer() {
//定制嵌入式的Servlet容器相关的规则
@Override
public void customize(ConfigurableEmbeddedServletContainer container)
{
container.setPort(8083);
}
};
}

模式:

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

3)、替换为其他嵌入式Servlet容器
默认支持:
Tomcat(默认使用)

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

Jetty


org.springframework.boot
spring‐boot‐starter‐web


spring‐boot‐starter‐tomcat
org.springframework.boot





spring‐boot‐starter‐jetty
org.springframework.boot

Undertow


spring‐boot‐starter‐undertow
org.springframework.boot

嵌入式Servlet容器工厂
TomcatServletWebServerFactory
JettyServletWebServerFactory
nettyServletWebServerFactory
undertowServletWebServerFactory

以TomcatServletWebServerFactory为例

     //创建一个tomcat
     Tomcat tomcat = new Tomcat();
     
     //配置Tomcat的基本环境
    File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    tomcat.getService().addConnector(connector);
    this.customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    this.configureEngine(tomcat.getEngine());
    Iterator var5 = this.additionalTomcatConnectors.iterator();

    while(var5.hasNext()) {
        Connector additionalConnector = (Connector)var5.next();
        tomcat.getService().addConnector(additionalConnector);
    }

    this.prepareContext(tomcat.getHost(), initializers);
    //将配置好的Tomcat传入进去,返回一个嵌入式的Servlet容器。并且getTomcatWebServer()方法中启动Tomcat服务器
    return this.getTomcatWebServer(tomcat);

4)、嵌入式Servlet容器自动配置原理
步骤:
1)、SpringBoot根据导入的依赖情况,给容器中添加相应的 EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】
(新版的TomcatServletWebServerFactory)嵌入式容器工厂创建出对象

2)、容器中某个组件要创建对象就会惊动后置处理器; EmbeddedServletContainerCustomizerBeanPostProcessor; 只要是嵌入式的Servlet容器工厂,后置处理器就工作;
3)、后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer,即调用定制器的定制方法

5)、嵌入式Servlet容器启动原理;
什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat;
获取嵌入式的Servlet容器工厂:
1)、SpringBoot应用启动运行run方法
2)、refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一 个组件】;
如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext,否则是默认的: AnnotationConfigApplicationContext容器
3)、refresh(context);刷新刚才创建好的ioc容器;
4)、 onRefresh(); web的ioc容器重写了onRefresh方法

5)、web ioc容器会创建嵌入式的Servlet容器;createEmbeddedServletContainer();
6)、获取嵌入式的Servlet容器工厂:
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
从ioc容器中获取EmbeddedServletContainerFactory 组件;TomcatEmbeddedServletContainerFactory创建 对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置;
7)、使用容器工厂获取嵌入式的Servlet容器:this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer());
8)、嵌入式的Servlet容器创建对象并启动Servlet容器;
先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来;

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

9、使用外置的Servlet容器

嵌入式Servlet容器:应用打成可执行的jar
优点:简单、便携;

缺点:默认不支持JSP、优化定制比较复杂(使用定制器【ServerProperties、自定义 EmbeddedServletContainerCustomizer】,自己编写嵌入式Servlet容器的创建工厂 【EmbeddedServletContainerFactory】);

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

原理 :
jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器;
war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器

servlet3.0(Spring注解版):
8.2.4 Shared libraries / runtimes pluggability:
规则:

1)、服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例:
2)、ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为 javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名
3)、还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;

流程:
1)、启动Tomcat
2)、org\springframework\spring-web\4.3.14.RELEASE\spring-web-4.3.14.RELEASE.jar!\METAINF\services\javax.servlet.ServletContainerInitializer: Spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer
3)、SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型 的类都传入到onStartup方法的Set>;为这些WebApplicationInitializer类型的类创建实例;
4)、每一个WebApplicationInitializer都调用自己的onStartup;

SpringBootServletInitializer 实现了WebApplicationInitializer接口方法
5)、相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法
6)、SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器

protected WebApplicationContext createRootApplicationContext( //创建容器
ServletContext servletContext) {
//1、创建SpringApplicationBuilder
SpringApplicationBuilder builder = createSpringApplicationBuilder();
StandardServletEnvironment environment = new StandardServletEnvironment();
environment.initPropertySources(servletContext, null);
builder.environment(environment);
builder.main(getClass());
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info(“Root context already created (using as parent).”);
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
builder.initializers(
new ServletContextApplicationContextInitializer(servletContext));
builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);

//调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来

builder = configure(builder);

//使用builder创建一个Spring应用

SpringApplication application = builder.build();
if (application.getSources().isEmpty() && AnnotationUtils
.findAnnotation(getClass(), Configuration.class) != null) {
application.getSources().add(getClass());
}
Assert.state(!application.getSources().isEmpty(),
"No SpringApplication sources have been defined. Either override the "
+ “configure method or add an @Configuration annotation”);
// Ensure error pages are registered
if (this.registerErrorPageFilter) {
application.getSources().add(ErrorPageFilterConfiguration.class);
}
//启动Spring应用
return run(application);
}

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

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

先启动Servlet容器(Tomcat、Jetty、JBoss、webLogic),再启动SpringBoot应用

ServletInitializer调用configure方法将SpringBoot的主程序类传入了进来,先启动Servlet容器,再启动SpringBoot应用
和jar包的方式相反

web容器(web服务器)主要有:Apache、IIS、Tomcat、Jetty、JBoss、webLogic等,而Tomcat、Jetty、JBoss、webLogic同时也是servlet容器,或者说他们还包含了servlet容器。没有servlet容器,你也可以用web容器直接访问静态页面,比如安装一个apache等,但是如果要显示jsp/servlet,你就要安装一个servlet容器了,但是光有servlet容器是不够的,因为它要被解析成html输出,所以你仍需要一个web容器。大多数servlet容器同时提供了web容器的功能,也就是说大多servelt容器可以独立运行你的web应用。


五、Docker
1、简介
Docker是一个开源的应用容器引擎;是一个轻量级容器技术;
Docker支持将软件编译成一个镜像;然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像;
运行中的这个镜像称为容器,容器启动是非常快速的。

docker主机(Host):安装了Docker程序的机器(Docker直接安装在操作系统之上);
docker客户端(Client):连接docker主机进行操作;
docker仓库(Registry):用来保存各种打包好的软件镜像;
docker镜像(Images):软件打包好的镜像;放在docker仓库中;
docker容器(Container):镜像启动后的实例称为一个容器;容器是独立运行的一个或一组应用

使用Docker的步骤:
1)、安装Docker
2)、去Docker仓库找到这个软件对应的镜像;
3)、使用Docker运行这个镜像,这个镜像就会生成一个Docker容器;
4)、对容器的启动停止就是对软件的启动停止;

3、安装Docker

1)、安装linux虚拟机

​ 1)、VMWare、VirtualBox(安装);

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

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

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

​ 5)、设置虚拟机网络;

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

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

service network restart

​ 7)、查看linux的ip地址

ip addr

​ 8)、使用客户端连接linux;

2)、在linux虚拟机上安装docker

步骤:

1、检查内核版本,必须是3.10及以上
uname -r
2、安装docker
yum install docker
3、输入y确认安装
4、启动docker
[root@localhost ~]# systemctl start docker
[root@localhost ~]# docker -v
Docker version 1.12.6, build 3e8e77d/1.12.6
5、开机启动docker
[root@localhost ~]# systemctl enable docker
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.
6、停止docker
systemctl stop docker

4、Docker常用命令&操作

1)、镜像操作

操作 命令 说明
检索 docker search 关键字 eg:docker search redis 我们经常去docker hub上检索镜像的详细信息,如镜像的TAG。
拉取 docker pull 镜像名:tag :tag是可选的,tag表示标签,多为软件的版本,默认是latest
列表 docker images 查看所有本地镜像
删除 docker rmi image-id 删除指定的本地镜像

https://hub.docker.com/

命令:
1、搜索镜像 [root@localhost ~]# docker search tomcat
2、拉取镜像 [root@localhost ~]# docker pull tomcat
3、根据镜像启动容器 docker run ‐‐name mytomcat ‐d tomcat:latest
–name:自定义容器名
-d:后台运行
image-name:指定镜像模板
4、docker ps 查看运行中的容器
5、 停止运行中的容器 docker stop 容器的id
6、查看所有的容器 docker ps ‐a
7、启动容器 docker start 容器id
8、删除一个容器 docker rm 容器id
9、启动一个做了端口映射的tomcat [root@localhost ~]# docker run ‐d ‐p 8888:8080 tomcat ‐d:后台运行 ‐p: 将主机的端口映射到容器的一个端口 主机端口:容器内部的端口
8888是虚拟机端口号,映射到容器Tomcat的8080

10、为了演示简单关闭了linux的防火墙 service firewalld status ;查看防火墙状态 service firewalld stop:关闭防火墙
11、查看容器的日志 docker logs container‐name/container‐id
更多命令参看 https://docs.docker.com/engine/reference/commandline/docker/
可以参考每一个镜像的文档

SpringBoot与数据访问
1、JDBC

org.springframework.boot spring-boot-starter-jdbc mysql mysql-connector-java runtime

spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://192.168.15.22:3306/jdbc
driver-class-name: com.mysql.jdbc.Driver


效果:

​	默认是用class com.zaxxer.hikari.HikariDataSource作为数据源;

​	数据源的相关配置都在DataSourceProperties里面;




效果:
 
 默认是用org.apache.tomcat.jdbc.pool.DataSource作为数据源;(老版本)
默认是用class com.zaxxer.hikari.HikariDataSource作为数据源(新版本);
  数据源的相关配置都在DataSourceProperties里面;
自动配置原理:
org.springframework.boot.autoconfigure.jdbc:
 1、参考DataSourceConfiguration,根据配置创建数据源,默认使用Tomcat连接池;(老版本)
可以使用 spring.datasource.type指定自定义的数据源类型
自定义数据源类型:c3p0,dbcp...; 




3、自定义数据源类型

/**
 * Generic DataSource configuration.
 */
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type")
static class Generic {

   @Bean
   public DataSource dataSource(DataSourceProperties properties) {
       //使用DataSourceBuilder创建数据源,利用反射创建响应type的数据源,并且绑定相关属性
      return properties.initializeDataSourceBuilder().build();
   }

}


4、DataSourceInitializer:ApplicationListener;
 作用:
  1)、runSchemaScripts();运行建表语句;
  2)、runDataScripts();运行插入数据的sql语句;
默认只需要将文件命名为:
schema‐*.sql、data‐*.sql 默认规则:
schema.sql,schema‐all.sql;


也 可以使用在配置文件中加
    schema:         
       ‐ classpath:department.sql       
指定位置



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


6。Springboot整合Druid

7。Springboot整合Druid+Mybatis

package com.itcast.springbootmybatis.config;


import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//自定义MyBatis的配置规则;给容器中添加一个ConfigurationCustomizer
@Configuration
public class MyBatisConfig {

    @Bean
    public ConfigurationCustomizer configurationCustomizer(){

        return new ConfigurationCustomizer() {
            @Override
            public void customize(org.apache.ibatis.session.Configuration configuration) {
                configuration.setMapUnderscoreToCamelCase(true); //开启驼峰命名法映射规则,当数据库中字段名是驼峰命名规则和对应Bean字段名不一致时,开启
            }
        };
    }
}




package com.itcast.springbootmybatis.mapper;

import com.itcast.springbootmybatis.bean.Department;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;

//指定这是一个操作数据库的mapper
//@Mapper
@Repository
public interface mapper {

    @Select("select * from department where id=#{id}")
    public Department getDeptById(Integer id);

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

    @Options(useGeneratedKeys = true,keyProperty = "id")  //主键自动生成,keyProperty告知哪个是主键
    @Insert("insert into department(departmentName) values(#{departmentName})")
    public int insertDept(Department department);

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


package com.itcast.springbootmybatis.controller;

import com.itcast.springbootmybatis.bean.Department;
import com.itcast.springbootmybatis.mapper.mapper;
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 DeptController {
    @Autowired
    mapper departmentMapper;

    @GetMapping("/dept/{id}")
    public Department getDepartment(@PathVariable("id") Integer id){
        return departmentMapper.getDeptById(id);
    }
    @GetMapping("/dept")
    public Department insertDept(Department department){

        departmentMapper.insertDept(department);
        return department;
    }
}


package com.itcast.springbootmybatis;

import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@ServletComponentScan
@MapperScan(value = "com.itcast.springbootmybatis.mapper")  //批量扫描整个mapper,避免每个Mapper文件下写@Mapper
public class SpringbootMybatisApplication {

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

}


注解版:
mybatis:
#  全局配置文件位置
  config-location: classpath:mybatis/mybatis-config.xml
#  sql映射文件位置
  mapper-locations: classpath:mybatis/mapper/*.xml
#      - classpath:employee.sql



package com.itcast.springbootmybatis.mapper;

import com.itcast.springbootmybatis.bean.Department;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;

//指定这是一个操作数据库的mapper
//@Mapper
@Repository
public interface mapper {

    @Select("select * from department where id=#{id}")
    public Department getDeptById(Integer id);

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

    @Options(useGeneratedKeys = true,keyProperty = "id")  //主键自动生成,keyProperty告知哪个是主键
    @Insert("insert into department(departmentName) values(#{departmentName})")
    public int insertDept(Department department);

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


配置文件:
全局配置文件




    

        
    




数据库文件



    

    
        INSERT into employee(lastName,email,gender,d_id) values (#{lastName},#{email},#{gender},#{dId})
    





 Spring Data
JPA的使用
package com.itcast.springbootjpa.controller;

import com.itcast.springbootjpa.entity.User;
import com.itcast.springbootjpa.repository.UserRepository;
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;

import java.util.Optional;

@RestController
public class UserController {
    @Autowired
    UserRepository userRepository;

//    @GetMapping("/user/{id}")
//    public Optional getUser(@PathVariable("id") Integer id){
//        Optional user=userRepository.findById(id);
//        return user;
//    }
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable("id") Integer id){

//        因为jsonplugin用的是java的内审机制.hibernate会给被管理的pojo加入一个hibernateLazyInitializer属性,
//        jsonplugin会把hibernateLazyInitializer也拿出来操作,并读取里面一个不能被反射操作的属性就产生了这个异常.
        User user = userRepository.findById(id).orElse(null); //需要在实体类中配置@JsonIgnoreProperties(value ="hibernateLazyInitializer" )

//        User one = userRepository.getOne(id);//使用此方法会报错500,原因未知,也可使用上面注释的方法
        return user;
    }
    @GetMapping("/user")
    public User insertUser(User user){
        User save = userRepository.save(user);
        return save;
    }
}


package com.itcast.springbootjpa.entity;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import javax.persistence.*;

//使用JPA注解 配置映射关系
@Entity //告诉JPA这是一个实体类(和数据表映射的类)
@Table(name = "tbl_user") //@Table来指定和哪个数据库表对应,如果name默认表名就是user;如果数据库此时没有则会自己创建数据库表
@JsonIgnoreProperties(value ="hibernateLazyInitializer" )
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) //自增主键
    private Integer id;
    @Column(name = "last_Name",length = 50) //这是和数据库表对应的一个列
    private String lastName;
    @Column //省略默认列名就是属性名
    private String email;

    public Integer getId() {
        return id;
    }

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

    public String getLastName() {
        return lastName;
    }

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

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}


package com.itcast.springbootjpa.repository;

import com.itcast.springbootjpa.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

//继承JpaRepository来完成对数据库的操作
public interface UserRepository extends JpaRepository {
    
}


spring:
  datasource:
    url: jdbc:mysql://192.168.0.191/jpa
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      #更新或者创建数据表结构
      ddl-auto: update
    #控制台显示sql
    show-sql: true




七、启动配置原理 
几个重要的事件回调机制
配置在META-INF/spring.factories 
ApplicationContextInitializer
SpringApplicationRunListener  
只需要放在ioc容器中 
ApplicationRunner 
CommandLineRunner

1、创建SpringApplication对象 

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



八、自定义starter 
starter:
1、这个场景需要使用到的依赖是什么? 
2、如何编写自动配置

@Configuration  //指定这个类是一个配置类
 @ConditionalOnXXX  //在指定条件成立的情况下自动配置类生效 
@AutoConfigureAfter  //指定自动配置类的顺序
 @Bean  //给容器中添加组件   
@ConfigurationPropertie结合相关xxxProperties类来绑定相关的配置
 @EnableConfigurationProperties //让xxxProperties生效加入到容器中  
 
 
  自动配置类要能加载 将需要启动就加载的自动配置类,配置在META‐INF/spring.factories      	   
  org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 
 org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ 
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\



3、模式:
 启动器(starter)只用来做依赖导入;
专门来写一个自动配置模块(XXXAutoConfigure);
启动器依赖自动配置;别人只需要引入启动器(starter)

mybatis-spring-boot-starter;
自定义启动器名-spring-boot-starter 



8.Cache缓存
package com.itguigu.springbootcache;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

/**
 * 快速体验缓存:
 *          步骤:开启基于注解的缓存
 *          2.标注缓存注解即可
 *          @CachePut 保证方法被调用,又希望结果被缓存。
 *          @CacheEvict  清空缓存
 *          @Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
 *
 *  需要另在启动类上加上@CacheEnable注解缓存才会生效
 *  原理:
 *      1.自动配置类CacheAutoConfiguration
 *      2.缓存的配置类
 *  0 = "org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration"
 * 1 = "org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration"
 * 2 = "org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration"
 * 3 = "org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration"
 * 5 = "org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration"
 * 4 = "org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration"
 * 6 = "org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration"
 * 7 = "org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration"
 * 8 = "org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration"
 * 9 = "org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration"
 *      3.默认SimpleCacheConfiguration生效
 *      4、给容器注册了一个CacheManager:ConcurrentMapCacheManager。
 *             可以获取和创建ConcurrentMapCache类型的缓存组件,他的作用将数据保存在ConcurrentMap(extends Map)中
 * 运行流程:
 * @Cacheable:
 *          1.方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取:
 *              (CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。
 *          2.去Cache中查找缓存的内容,使用一个key,默认就是方法的参数:
 *                  key是按照某种策略生成的:默认是使用keyGenerator生成的,默认是使用SimpleKeyGenerator生成的key
 *                      SimpleKeyGenerator生成key的默认策略:
 *                             如果没有参数:key=new SimpleKey();
 *                             如果有一个参数:key=参数的值
 *                             如果有多个参数:key=new SimpleKey(params);
 *          3.没有查询到缓存就调用目标方法
 *          4.将目标方法返回的结果,放进缓存中
 * @Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存
 * 如果没有,就运行方法并将结果放入缓存,以后再来调用就可以直接使用缓存中的数据
 *
 * 核心:
 *      1.使用CacheManager(ConcurrentMapCacheManager)按照名字得到cache(ConcurrentMapCache)组件
 *      2.key使用keyGenerator生成的,默认是SimpleKeyGenerator
 *
 */

@EnableCaching
@MapperScan("com.itguigu.springbootcache.mapper")  ////批量扫描整个mapper,避免每个Mapper文件下写@Mapper
@SpringBootApplication
public class SpringbootCacheApplication {

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

}






package com.itguigu.springbootcache.service;

import com.itguigu.springbootcache.bean.Employee;
import com.itguigu.springbootcache.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;

    /**
     * 将方法的运行结果进行缓存,以后再要相同的数据,直接从缓存获取,不用调用方法;
     *
     * CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字
     *      几个属性:
     *              cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存
     *              key:缓存数据使用的key;可以用它来指定。默认是使用方法参数的值   1-方法的返回值
     *                  编写SpEL:  #id:参数id的值   #a0   #p0  #root.args[0]
     *              keyGenerator:key的生成器:可以自己指定key的生成器的组件id
     *                           key/keyGenerator:二选一使用
     *
     *              cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器
     *              condition:指定符合条件的情况下缓存
     *                  condition = "#a0>1" 第一个参数的值大于1才进行缓存
     *              unless:否定缓存:当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断(#result取出结果的值)
     *                   unless="#a0==2":假如第一个参数的值是2,结果不缓存
     *              sync:是否使用异步模式
     *                  当sync等于true时,unless则不能使用
     * @param id,key = "#id",condition = "#id>0",unless = "#result==null"
     * @return
     */
    //@Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator")  //key = "#root.methodName+'['+#id+']'"自定义key
    @Cacheable(cacheNames = "emp")  //key = "#root.methodName+'['+#id+']'"自定义key
    public Employee getEmp(Integer id){
        System.out.println("查询"+id+"号员工");
        Employee emp = employeeMapper.getEmpById(id);
        return emp;
    }
    /**
     * @CachePut:既调用方法,又更新缓存数据
     * 修改了数据库的某个数据,同时更新缓存
     *      运行时机:先调用目标方法,将方法的结果缓存起来
     *      测试步骤:
     *          1.查询1号员工,查到的结果会放在缓存中
     *                  key;1  value:lastname:wangwu   缓存是通过键值对的方式保存的
     *          2.以后查询还是之前的结果
     *          3.更新1号员工 [ lastName:keliu ]
     *                  将方法的返回值也放进了缓存中
     *                  而此时key:传入的employee对象  值:返回的employee对象  缓存中之前的key没有更新,需要在参数中添加key的值
     *
     *          4.查询一号员工:
     *             应该是更新后的员工,结果是更新前的员工信息   (原因因为更新员工时,key为指定为之前缓存的key,所以之前缓存的key没有更新)
     *                key="#employee.id" :使用传入的参数的员工id;
     *                key="#result.id"  使用返回后的id    (这两个效果一样,因为@Cacheput是方法完成后将结果缓存起来)
     *                   @Cacheable的key是不能用#result
     */
    @CachePut(value = "emp",key = "#result.id")
    public Employee updateEmp(Employee employee){
        System.out.println("updateEmp"+employee);
        employeeMapper.updateEmp(employee);
        return employee;
    }
    /**
     * @CacheEvict:缓存清除
     *      key:指定要清除的缓存数据
     *      allEntries = true  清空缓存中所有数据
     *      beforeInvocation=false :缓存的清除是否在方法之前执行
     *          默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
     *      beforeInvocation=true
     *      *          默认代表缓存清除操作是在方法执行之前执行;无聊是否出现异常,缓存都清除
     */
    @CacheEvict(value = "emp",key = "#id")//allEntries = true  清空缓存中所有数据
    public void deleteEmp(Integer id){
        System.out.println("deleteEmp"+id);
        //employeeMapper.delEmp(id);
    }

    //@CachePut这个注解是在方法执行之后执行的,所以被注解的方法一定会执行
    @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);
    }
}






package com.itguigu.springbootcache.controller;

import com.itguigu.springbootcache.bean.Employee;
import com.itguigu.springbootcache.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 emp = employeeService.getEmp(id);
        return emp;
    }
    @GetMapping("/emp")
    public Employee updateEmp(Employee employee){
        Employee employee1 = employeeService.updateEmp(employee);
        return employee1;

    }
    @GetMapping("/del")
    public String delEmp(Integer id){
        employeeService.deleteEmp(id);
        return "success";
    }

    @GetMapping("/emp/lastName/{lastName}")
    public Employee getEmpByLastName(@PathVariable("lastName") String lastName){
       return employeeService.getEmpByLastName(lastName);
    }
}




redis:
redis:0>append msg hello
"5"
redis:0>append msg world
"10"

redis:0>get msg
"helloworld"

redis:0>lpush mylist 1 2 3 4 5
"5"

redis:0>lpop mylist
"5"

redis:0>rpop mylist
"1"

redis:0>sadd myset zhangsan lisi
"2"

redis:0>sadd myset lisi
"0"

redis:0>smembers myset
1) "lisi"
2) "zhangsan"
redis:0>sismember myset wangwu    判断元素在不在myset集合中
"0"



* 三:整合redis作为缓存
 * Redis是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以作为数据库,缓存和消息中间件
 *      1.安装redis,使用docker  :docker pull registry.docker-cn.com/library/redis
 *     2.引入redis的starter
 *     3.配置redis
 *     4.测试缓存
 *          原理:CacheManager创建了cache缓存组件,缓存组件来实际给缓存中进行存取数据
 *          1.引入redis的starter,容器中保存的是RedisCacheManager
 *          2.RedisCacheManager 帮我们创建RedisCache来作为缓存组件,RedisCache通过操作redis缓存数据
 *          3.默认保存数据k-v都是object:利用序列化来保存:如何保存为json
 *                 1.引入了redis的starter,cacheManager变为RedisCacheManager
 *                 2.默认创建的RedisCacheManager操作redis的时候使用的是     			   RedisTemplate
 *                 3.RedisTemplate是默认使用jdk的序列化机制
 *                 4.自定义cacheManager:将redis存储的缓存转化为json格式
 */

 MyRedisConfig.class
 
 
package com.itguigu.springbootcache.config;

import com.itguigu.springbootcache.bean.Employee;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import java.net.UnknownHostException;
import java.time.Duration;

@Configuration
public class MyRedisConfig {

    //提供给测试代码使用的RedisTemplate,可注释,主要用下面
    @Bean
    public RedisTemplate employeeRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer employeeJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Employee.class);
        template.setDefaultSerializer(employeeJackson2JsonRedisSerializer);
        return template;
    }
    
    
     4.自定义cacheManager:
    //创新新的CacheManager使redis数据缓存保存为json格式
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory){
        RedisCacheConfiguration cacheConfiguration=RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofDays(1))
                                                            .disableCachingNullValues().serializeValuesWith(RedisSerializationContext
                                                            .SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())  );

        return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build();

    }
}





package com.itguigu.springbootcache.service;

import com.itguigu.springbootcache.bean.Department;
import com.itguigu.springbootcache.mapper.DepartmentMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.stereotype.Service;

@Service
public class DepartmentService {

    @Autowired
    DepartmentMapper departmentMapper;

    @Cacheable(cacheNames = "dept")
    public Department getDeptById(Integer id){
        System.out.println("查询部门"+id);
         Department department=departmentMapper.getDeptById(id);
         return department;
    }
}



package com.itguigu.springbootcache.controller;

import com.itguigu.springbootcache.bean.Department;
import com.itguigu.springbootcache.service.DepartmentService;
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 DeptController {
    @Autowired
    DepartmentService departmentService;
    @GetMapping("/dept/{id}")
    public Department getDept(@PathVariable("id") Integer id){
        Department deptById = departmentService.getDeptById(id);
        return deptById;
    }
}



package com.itguigu.springbootcache;

import com.itguigu.springbootcache.bean.Employee;
import com.itguigu.springbootcache.mapper.EmployeeMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;

@SpringBootTest
class SpringbootCacheApplicationTests {

    @Autowired
    EmployeeMapper employeeMapper;

    @Autowired
    StringRedisTemplate stringRedisTemplate;  //操作k-v都是字符串
    @Autowired
    RedisTemplate redisTemplate;  //k-v都是对象的
    @Autowired
    RedisTemplate employeeRedisTemplate;

    /**
     * Redis常见的五大数据操作类型
     *        String(字符串):stringRedisTemplate.opsForValue();  操作字符串的
     *        List(列表),stringRedisTemplate.opsForList() 操作列表
     *        Set(集合),  stringRedisTemplate.opsForSet()  操作Set
     *        Hash(散列),stringRedisTemplate.opsForHash()
     *        ZSet(有序集合)stringRedisTemplate.opsForZSet() 操作有序集合
     */
    @Test
    public void test1(){
        //给redis保存数据
//        stringRedisTemplate.opsForValue().append("msg","hello");
//        String msg = stringRedisTemplate.opsForValue().get("msg");
//        System.out.println(msg);

        stringRedisTemplate.opsForList().leftPush("mylist","1");
        stringRedisTemplate.opsForList().leftPush("mylist","2");
    }
    @Test
    public void test02(){
        Employee empById = employeeMapper.getEmpById(2);
        //默认如果保存对象,使用jdk序列化机制,序列化后的数据保存到redis中
       // redisTemplate.opsForValue().set("emp-01",empById);
        //1.将数据以json的方式保存
            //(1)自己讲对象转换为json
           // (2)redisTemplate默认的序列化规则:改变默认的序列化规则(默认:JdkSerializationRedisSerializer)
           //改成Jackson2JsonRedisSerializer,详见MyRedisConfig
        employeeRedisTemplate.opsForValue().set("emp-01",empById);
    }

    @Test
    void contextLoads() {
        Employee empById = employeeMapper.getEmpById(1);
        System.out.println(empById);
    }

}


#使用了Mysql Connector/J 6.x以上的版本,然后就报了时区的错误,解决方法:
#    在配置url的时候不能简单写成 :
#    jdbc:mysql://localhost:3306/yzu
#    而是要写成 :
#    jdbc:mysql://localhost:3306/yzu?serverTimezone=UTC

spring.datasource.url=jdbc:mysql://localhost:3306/springboot-cache?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#开启驼峰命名法
mybatis.configuration.map-underscore-to-camel-case=true
#打印mapper包下的日志
logging.level.com.itguigu.springbootcache.mapper=debug

#debug=true

spring.redis.host=192.168.0.191




Spring Boot与消息
1. 大多应用中,可通过消息服务中间件来提升系统异步通信、扩展解耦能力 
2. 消息服务中两个重要概念: 消息代理(message broker)和目的地(destination) 当消息发送者发送消息以后,将由消息代理接管,消息代理保证消息传递到指定目 的地。
 3. 消息队列主要有两种形式的目的地 1. 队列(queue):点对点消息通信(point-to-point) 2. 主题(topic):发布(publish)/订阅(subscribe)消息通信

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

MQ:消息队列
解耦,异步,削峰/限流

https://developer.51cto.com/art/201904/595020.htm

RabbitMQ简介: RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue Protocol)的开源实现。
核心概念 Message 消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组 成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出 该消息可能需要持久性存储)等。

Publisher 消息的生产者,也是一个向交换器(服务器里面的交换器)发布消息的客户端应用程序。

Exchange 交换器(消息队列服务器里面的交换器),用来接收生产者发送的消息并将这些消息(根据消息中的路由键routing-key)路由给服务器中的队列。 Exchange有4种类型:direct(默认),fanout, topic, 和headers,不同类型的Exchange转发消息的策略有 所区别

Queue 消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息 可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。

Binding 绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连 接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。 Exchange 和Queue的绑定可以是多对多的关系。

Connection 网络连接,比如一个TCP连接。
 Channel 信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内的虚 拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这 些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所 以引入了信道的概念,以复用一条 TCP 连接。

Consumer 消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。

Virtual Host 虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加 密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有 自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定, RabbitMQ 默认的 vhost 是 / 。
 Broker 表示消息队列服务器实体


Exchange 类型
• Exchange分发消息时根据类型的不同分发策略有区别,目前共四种类型: direct、fanout、topic、headers 。headers 匹配 AMQP 消息的 header 而不是路由键, headers 交换器和 direct 交换器完全一致,但性能差很多, 目前几乎用不到了,所以直接看另外三种类型:

Direct Exchange

消息中的路由键(routing key)如果和 Binding 中的 binding key 一致, 交换器就将消息发到对应的队列中。路由键与队 列名完全匹配,如果一个队列绑定到交换机要求路由键为 “dog”,则只转发 routing key 标记为“dog”的消息,不会转 发“dog.puppy”,也不会转发“dog.guard”等等。它是完全 匹配、单播的模式。

Fanout Exchange

每个发到 fanout 类型交换器的消息都会分到所 有绑定的队列上去。fanout 交换器不处理路由键, 只是简单的将队列绑定到交换器上,每个发送 到交换器的消息都会被转发到与该交换器绑定 的所有队列上。很像子网广播,每台子网内的 主机都获得了一份复制的消息。fanout 类型转发 消息是最快的。

Topic Exchange

topic 交换器通过模式匹配分配消息的路由键属 性,将路由键和某个模式进行匹配,此时队列 需要绑定到一个模式上。它将路由键和绑定键 的字符串切分成单词,这些单词之间用点隔开。 它同样也会识别两个通配符:符号“#”和符号 “* ” 。 # 匹配 0 个或多个单词 , *匹配一个单词



package com.itcast.springbootamqp;

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发送和接受消息
 *    5.AmqpAdmin:RabbitMQ的系统管理功能组件
 *          AmqpAdmin:创建和删除 Queue,Exchange,Binding
 *    6.@EnableRabbit +@RabbitListener监听消息队列的内容
 *
 */
@EnableRabbit  //开启基于注解的RabbitMQ模式
@SpringBootApplication
public class SpringbootAmqpApplication {

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

}


spring.rabbitmq.host=192.168.0.191
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

#可以不写,直接使用用默认值
#spring.rabbitmq.port=5672
#spring.rabbitmq.virtual-host="/"



 4.RabbitTemplate:给RabbitMQ发送和接受消息
 *    5.AmqpAdmin:RabbitMQ的系统管理功能组件
 *          AmqpAdmin:创建和删除 Queue,Exchange,Binding
 *    6.@EnableRabbit +@RabbitListener监听消息队列的内容

package com.itcast.springbootamqp;

import com.itcast.springbootamqp.bean.book;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@SpringBootTest
class SpringbootAmqpApplicationTests {

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    AmqpAdmin amqpAdmin;

    @Test
    public void createExchange(){    //创建队列,Exchange,绑定规则
        //创建交换器
//        amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange"));
//        System.out.println("创建完成");
        //创建队列
        //amqpAdmin.declareQueue(new Queue("amqpadmin.queue",true));

        //创建binding规则
        amqpAdmin.declareBinding(new Binding("amqpadmin.queue", Binding.DestinationType.QUEUE,
                "amqpadmin.exchange","amqp.haha",null));
    }
    /**
     * 1.单播(点对点)
     */
    @Test
    void contextLoads() {
        //message需要自己构造一个;定义消息体内容和消息头
//        rabbitTemplate.send(exchange,routKey,message);

        //object默认当成消息体,只需要传入要发送的对象,自动序列化发送给rabbitmq;
        //rabbitTemplate.convertAndSend(exchange,routKey,object)  其中exchange和routKey都是交换器exchange的属性,object是要传递的内容;
        Map map=new HashMap<>();
        map.put("msg","这是第一个消息");
        map.put("data", Arrays.asList("helloworld",123,true));
        //对象被默认序列化以后发送出去
        rabbitTemplate.convertAndSend("exchange.direct","atguigu.news",new book("一千","华艺轩"));
    }

    //接收数据,
    @Test
    public void receive(){
        Object o = rabbitTemplate.receiveAndConvert("atguigu.news");
        System.out.println(o.getClass());
        System.out.println(o);
    }

    /**
     * 广播
     */
    @Test
    public void sendMsg(){
        rabbitTemplate.convertAndSend("exchange.fanout","",new book("水浒传","hyx"));
    }
}





一、检索
我们的应用经常需要添加检索功能,开源的 ElasticSearch 是目前全文搜索引擎的 首选。他可以快速的存储、搜索和分析海量数据。
Spring Boot通过整合Spring Data ElasticSearch为我们提供了非常便捷的检索功能支持;
Elasticsearch是一个分布式搜索服务,提供Restful API,底层基于Lucene,采用 多shard(分片)的方式保证数据安全,并且提供自动resharding的功能,github 等大型的站点也是采用了ElasticSearch作为其搜索服务,



• 以员工文档 的形式存储为例:一个文档代表一个员工数据。存储数据到 ElasticSearch 的行为叫做 索引 ,但在索引一个文档之前,需要确定将文档存 储在哪里。
 • 一个 ElasticSearch 集群可以 包含多个 索引 ,相应的每个索引可以包含多 个 类型 。 这些不同的类型存储着多个 文档 ,每个文档又有 多个 属性 。
 • 类似关系:
 – 索引-数据库 – 类型-表 – 文档-表中的记录 – 属性-列






package com.itcast.springbootelasticsearch;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Springboot默认支持使用两种技术和ES交互
 *      1.Jest(默认不生效,已经过时)
 *      2.SpringData ElasticSearch
 *          1)Client(ElasticsearchAutoConfiguration)  如果使用需要配置 节点信息clusterNodes  clusterName
 *          2)ElasticTemplate  操作ES
 *          3)编写一个ElasticsearchRepository的子接口来操作ES
 */


@SpringBootApplication
public class SpringbootElasticsearchApplication {

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

}


package com.itcast.springbootelasticsearch.repository;

import com.itcast.springbootelasticsearch.bean.book;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import java.util.List;

public interface BookRepository extends ElasticsearchRepository {//book的主键是Integer类型

        //根据官方文档,只需要根据代码提示写方法名称(参考官方文档),不用写具体实现
        public List findByBookNameLike(String bookName);

}


spring.data.elasticsearch.cluster-name=elasticsearch
spring.data.elasticsearch.cluster-nodes=192.168.0.191:9300



package com.itcast.springbootelasticsearch;

import com.itcast.springbootelasticsearch.bean.book;
import com.itcast.springbootelasticsearch.repository.BookRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
class SpringbootElasticsearchApplicationTests {

    @Autowired
    BookRepository bookRepository;
    @Test
    void contextLoads() {
        book book=new book();
        book.setId(1);
        book.setBookName("西游记女");
        book.setAuthor("hyx");

        bookRepository.index(book);  //book存储在哪个索引哪个类型下一定要标注

        for(book book1:bookRepository.findByBookNameLike("游记")){
            System.out.println(book1);
        }

        List as = bookRepository.findByBookNameLike("游记");
        System.out.println(as);
    }

}



任务

异步任务
package com.itcast.springboottask.service;

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

@Service
public class AsyncService {

    @Async  //告诉Spring这是一个异步方法,还需在主类中添加注解 @EnableAsync 开启异步注解
    public void hello(){

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("处理数据中");
    }

}

定时任务
package com.itcast.springboottask.service;

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

@Service
public class ScheduledService {

    /**
     * 秒,分,时,日,月,周几
     * 【0 0/5 14,18 * * ?】 每天14点整,和18点整,每隔15分钟执行一次
     * [0 15 10 ? * 1-6]  每个月的周一至周六10:15执行一次
     * [0 0 2 ? * 6L]   每个月的最后一个周六凌晨2点执行一次
     * [0 0 2 LW * ?]   每个月的最后一个工作日凌晨2点执行一次
     * [0 0 2-4 ? * 1#1] 每个月的第一个周一凌晨2点到4点期间,每个整点执行一次
     */
    //@Scheduled(cron ="0 * * * * MON-WED")  //以空格为分隔符
    @Scheduled(cron = "0,1,2,3,4 * * * * MON-WED")
   // @Scheduled(cron = "0-4 * * * * MON-WED")
    //@Scheduled(cron = "0/4 * * * * MON-WED") //每四秒打印一个
    public void hello1(){
        System.out.println("hello");
    }
}

package com.itcast.springboottask;

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

@EnableScheduling//开启定时任务
@EnableAsync  //开启异步注解
@SpringBootApplication
public class SpringbootTaskApplication {

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



邮件任务

[email protected]
spring.mail.password=asfafsdgfgsfsfsff
spring.mail.host=smtp.qq.com
#开启SSL
spring.mail.properties.mail.smtp.ssl.enable=true   


package com.itcast.springboottask;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;

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

@SpringBootTest
class SpringbootTaskApplicationTests {

    @Autowired
    JavaMailSender mailSender;

    @Test
    void contextLoads() {

        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        //邮件设置
        simpleMailMessage.setSubject("通知");
        simpleMailMessage.setText("今晚开会");
        simpleMailMessage.setTo("[email protected]");
        simpleMailMessage.setFrom("[email protected]");
        mailSender.send(simpleMailMessage);
    }
    @Test
    public void test02() throws MessagingException {
        //1.创建一个复杂的消息邮件
        MimeMessage mimeMailMessage=mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMailMessage, true);
        //邮件设置
        helper.setSubject("通知");
        helper.setText("今晚开会",true);
        helper.setTo("[email protected]");
        helper.setFrom("[email protected]");
        //上传附件
        helper.addAttachment("1.jpg",new File(""));


        mailSender.send(mimeMailMessage);

    }

}




Spring Boot与分布式
• ZooKeeper (注册中心)
ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务。它是 一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、 域名服务、分布式同步、组服务等。

 • Dubbo (分布式框架)
Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方 式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合)。从服务模型的角度来看,Dubbo采用的是一种非常简单的模型,要么是提供方提供服务,要么是消费方消费服务,所以基于这一点可以抽象出服务提供方(Provider)和服务消费方(Consumer)两个角色。

Provider
dubbo.application.name=provider-ticket

#注册中心地址
dubbo.registry.address=zookeeper://192.168.0.191:2181

#将哪个包下的服务发布出去
dubbo.scan.base-packages=com.itguigu.providerticket.service


package com.itguigu.providerticket;

import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


/**
 *      1.将服务提供者注册到注册中心
 *          1.引入dubbo和zkclient相关依赖
 *          2.配置dubbo的扫描包和注册中心地址
 

    com.alibaba.boot
    dubbo-spring-boot-starter
    0.2.0



    com.101tec
    zkclient
    0.7

 *          3.使用@Service发布服务
 */
@EnableDubbo
@SpringBootApplication
public class ProviderTicketApplication {

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

}


package com.itguigu.providerticket.service;

import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.stereotype.Component;

@EnableDubbo
@Component //添加到容器中
@Service //将服务发布出去,注意此处注解是dubbo的注解
public class TicketServiceImpl implements TicketService {
    @Override
    public String getTicket() {
        return "厉害了我的国";
    }
}


Consumer
dubbo.application.name=consumer-user

#注册中心地址
dubbo.registry.address=zookeeper://192.168.0.191:2181


package com.itguigu.consumertest.service;


import com.alibaba.dubbo.config.annotation.Reference;
import com.itguigu.providerticket.service.TicketService;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Reference   //远程引用,按照全类名进行匹配的
    TicketService ticketService;

    public void hello(){
        String ticket = ticketService.getTicket();

        System.out.println("买到票了: "+ticket);
    }

}


package com.itguigu.consumertest;

import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 1.引入依赖:
 *      1.引入dubbo和zkclient相关依赖
 *  *   2.配置dubbo的扫描包和注册中心地址
 */
@EnableDubbo
@SpringBootApplication
public class ConsumerTestApplication {

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

}

package com.itguigu.consumertest;

import com.itguigu.consumertest.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ConsumerTestApplicationTests {

    @Autowired
    UserService userService;
    @Test
    void contextLoads() {

        userService.hello();
    }

}



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





一、热部署
在开发中我们修改一个Java文件后想看到效果不得不重启应用,这导致大量时间 花费,我们希望不重启应用的情况下,程序可以自动部署(热部署)。

、Spring Boot Devtools(推荐) – 引入依赖

  
      org.springframework.boot 
      spring-boot-devtools   

 
– IDEA使用ctrl+F9 – 或做一些小调整 Intellij IEDA 和 Eclipse 不同, Eclipse 设置了自动编译之后,修改类它会自动编译 ,而 IDEA 在非 RUN 或 DEBUG 情况下 才会自动编译(前提是你已经设置了 Auto - Compile )。 • 设置自动编译(settings-compiler-make project automatically) • ctrl+shift+alt+/(maintenance) • 勾选compiler.automake.allow.when.app.running










你可能感兴趣的:(SpringBoot初级踩坑)