SpringBoot学习笔记ANTIA11

Spring是一个开源框架,2003年兴起的一个轻量级的Java开发框架,作者:RodJohnson。

Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。

为了降低Java开发的复杂性,Spring采用了以下4种关键策略:

1、基于POJO的轻量级和最小侵入性编程,所有东西都是bean;

2、通过IOC,依赖注入(DI)和面向接口实现松耦合;

4、通过切面和模版减少样式代码,RedisTemplate,xxxTemplate;

学过javaweb的同学就知道,开发一个web应用,从最初开始接触Servlet结合Tomcat,跑出一个HelloWolrld程序,是要经历特别多的步骤;后来就用了框架Struts,再后来是SpringMVC,到了现在的SpringBoot,过一两年又会有其他web框架出现;你们有经历过框架不断的演进,然后自己开发项目所有的技术也在不断的变化、改造吗?建议都可以去经历一遍;

所有的技术框架的发展似乎都遵循了一条主线规律:从一个复杂应用场景衍生一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。

是的这就是Java企业级应用->J2EE->spring->springboot的过程。

随着Spring不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。SpringBoot正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用Spring、更容易的集成各种常用的中间件、开源软件;

SpringBoot基于Spring开发,SpirngBoot本身并不提供Spring框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于Spring框架的应用程序。也就是说,它并不是用来替代Spring的解决方案,而是和Spring框架紧密结合用于提升Spring开发者体验的工具。SpringBoot以约定大于配置的核心思想,默认帮我们进行了很多设置,多数SpringBoot应用只需要很少的Spring配置。同时它集成了大量常用的第三方库配置(例如Redis、MongoDB、Jpa、RabbitMQ、Quartz等等),SpringBoot应用中这些第三方库几乎可以零配置的开箱即用。

简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,springboot整合了所有的框架。

SpringBoot的主要优点:

真的很爽,我们快速去体验开发个接口的感觉吧!

我们将学习如何快速的创建一个SpringBoot应用,并且实现一个简单的Http请求处理。通过这个例子对SpringBoot有一个初步的了解,并体验其结构简单、开发快速的特性。

我的环境准备:

开发工具:

Spring官方提供了非常方便的工具让我们快速构建应用

项目创建方式一:使用SpringInitializr的Web页面创建项目

2、填写项目信息

3、点击”GenerateProject“按钮生成项目;下载此项目

4、解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕。

5、如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。

项目创建方式二:使用IDEA直接创建项目

1、创建一个新项目

2、选择springinitalizr,可以看到默认就是去官网的快速构建工具那里实现

3、填写项目信息

4、选择初始化的组件(初学勾选Web即可)

5、填写项目路径

6、等待项目构建成功

项目结构分析:

通过上面步骤完成了基础项目的创建。就会自动生成以下文件。

1、程序的主启动类

2、一个application.properties配置文件

3、一个测试类

4、一个pom.xml

打开pom.xml,看看SpringBoot项目的依赖:

2、在包中新建一个HelloController类

@RestControllerpublicclassHelloController{@RequestMapping("/hello")publicStringhello(){return"HelloWorld";}}3、编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台输出了Tomcat访问的端口号!

简单几步,就完成了一个web接口的开发,SpringBoot就是这么简单。所以我们常用它来建立我们的微服务项目!

如果遇到以上错误,可以配置打包时跳过项目运行测试用例

org.apache.maven.pluginsmaven-surefire-plugintrue如果打包成功,则会在target目录下生成一个jar包

打成了jar包后,就可以在任何地方运行了!OK

彩蛋

如何更改启动时显示的字符拼成的字母,SpringBoot呢?也就是banner图案;

只需一步:到项目下的resources目录下新建一个banner.txt即可。

我们之前写的HelloSpringBoot,到底是怎么运行的呢,Maven项目,我们一般从pom.xml文件探究起;

父依赖

其中它主要是依赖一个父项目,主要是管理项目的资源过滤及插件!

org.springframework.bootspring-boot-starter-parent2.2.5.RELEASE点进去,发现还有一个父依赖

org.springframework.bootspring-boot-dependencies2.2.5.RELEASE../../spring-boot-dependencies这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;

以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;

org.springframework.bootspring-boot-starter-webspringboot-boot-starter-xxx:就是spring-boot的场景启动器

spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件;

分析完了pom.xml来看看这个启动类

默认的主启动类

//@SpringBootApplication来标注一个主程序类//说明这是一个SpringBoot应用@SpringBootApplicationpublicclassSpringbootApplication{publicstaticvoidmain(String[]args){//以为是启动了一个方法,没想到启动了一个服务SpringApplication.run(SpringbootApplication.class,args);}}但是一个简单的启动类并不简单!我们来分析一下这些注解都干了什么

@SpringBootApplication

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

进入这个注解:可以看到上面还有很多其他注解!

@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters={@Filter(type=FilterType.CUSTOM,classes={TypeExcludeFilter.class}),@Filter(type=FilterType.CUSTOM,classes={AutoConfigurationExcludeFilter.class})})public@interfaceSpringBootApplication{//......}@ComponentScan

这个注解在Spring中很重要,它对应XML配置中的元素。

作用:自动扫描并加载符合条件的组件或者bean,将这个bean定义加载到IOC容器中

@SpringBootConfiguration

作用:SpringBoot的配置类,标注在某个类上,表示这是一个SpringBoot的配置类;

我们继续进去这个注解查看

//点进去得到下面的@Component@Configurationpublic@interfaceSpringBootConfiguration{}@Componentpublic@interfaceConfiguration{}这里的@Configuration,说明这是一个配置类,配置类就是对应Spring的xml配置文件;

里面的@Component这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!

我们回到SpringBootApplication注解中继续看。

@EnableAutoConfiguration

@EnableAutoConfiguration:开启自动配置功能

以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;

点进注解接续查看:

@AutoConfigurationPackage:自动配置包

@Import({Registrar.class})public@interfaceAutoConfigurationPackage{}@import:Spring底层注解@import,给容器中导入一个组件

Registrar.class作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器;

这个分析完了,退到上一步,继续看

@Import({AutoConfigurationImportSelector.class}):给容器导入组件;

AutoConfigurationImportSelector:自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击去这个类看源码:

1、这个类中有一个这样的方法

//获得候选的配置protectedListgetCandidateConfigurations(AnnotationMetadatametadata,AnnotationAttributesattributes){//这里的getSpringFactoriesLoaderFactoryClass()方法//返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfigurationListconfigurations=SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(),this.getBeanClassLoader());Assert.notEmpty(configurations,"NoautoconfigurationclassesfoundinMETA-INF/spring.factories.Ifyouareusingacustompackaging,makesurethatfileiscorrect.");returnconfigurations;}2、这个方法又调用了SpringFactoriesLoader类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames()方法

publicstaticListloadFactoryNames(Class<>factoryClass,@NullableClassLoaderclassLoader){StringfactoryClassName=factoryClass.getName();//这里它又调用了loadSpringFactories方法return(List)loadSpringFactories(classLoader).getOrDefault(factoryClassName,Collections.emptyList());}3、我们继续点击查看loadSpringFactories方法

spring.factories

我们根据源头打开spring.factories,看到了很多自动配置的文件;这就是自动配置根源所在!

WebMvcAutoConfiguration

我们在上面的自动配置类随便找一个打开看看,比如:WebMvcAutoConfiguration

可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean,可以找一些自己认识的类,看着熟悉一下!

所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中对应的org.springframework.boot.autoconfigure.包下的配置项,通过反射实例化为对应标注了@Configuration的JavaConfig形式的IOC容器配置类,然后将这些都汇总成为一个实例并加载到IOC容器中。

结论:

现在大家应该大概的了解了下,SpringBoot的运行原理,后面我们还会深化一次!

不简单的方法

我最初以为就是运行了一个main方法,没想到却开启了一个服务;

@SpringBootApplicationpublicclassSpringbootApplication{publicstaticvoidmain(String[]args){SpringApplication.run(SpringbootApplication.class,args);}}SpringApplication.run分析

分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行;

SpringApplication

这个类主要做了以下四件事情:

1、推断应用的类型是普通的项目还是Web项目

2、查找并加载所有可用初始化器,设置到initializers属性中

3、找出所有的应用程序监听器,设置到listeners属性中

4、推断并设置main方法的定义类,找到运行的主类

查看构造器:

publicSpringApplication(ResourceLoaderresourceLoader,Class...primarySources){//......this.webApplicationType=WebApplicationType.deduceFromClasspath();this.setInitializers(this.getSpringFactoriesInstances();this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass=this.deduceMainApplicationClass();}run方法流程分析

配置文件

SpringBoot使用一个全局的配置文件,配置文件名称是固定的

application.properties

application.yml

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

比如我们可以在配置文件中修改Tomcat默认启动的端口号!测试一下!

server.port=80814.2、yaml概述YAML是"YAMLAin'taMarkupLanguage"(YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML的意思其实是:"YetAnotherMarkupLanguage"(仍是一种标记语言)

这种语言以数据作为中心,而不是以标记语言为重点!

以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml

传统xml配置:

8081yaml配置:

server:prot:80804.3、yaml基础语法说明:语法要求严格!

1、空格不能省略

2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。

3、属性和值的大小写都是十分敏感的。

字面量:普通的值[数字,布尔值,字符串]

字面量直接写在后面就可以,字符串默认不用加上双引号或者单引号;

k:v注意:

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

比如:name:"kuang\nshen"输出:kuang换行shen

''单引号,会转义特殊字符,特殊字符最终会变成和普通字符一样输出

比如:name:‘kuang\nshen’输出:kuang\nshen

对象、Map(键值对)

#对象、Map格式k:v1:v2:在下一行来写对象的属性和值得关系,注意缩进;比如:

student:name:qinjiangage:3行内写法

student:{name:qinjiang,age:3}数组(List、set)

用-值表示数组中的一个元素,比如:

pets:-cat-dog-pig行内写法

pets:[cat,dog,pig]修改SpringBoot的默认端口号

配置文件中添加,端口号的参数,就可以切换端口;

server:port:80824.4、注入配置文件yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!

yaml注入配置文件

1、在springboot项目中的resources目录下新建一个文件application.yml

2、编写一个实体类Dog;

packagecom.kuang.springboot.pojo;@Component//注册bean到容器中publicclassDog{privateStringname;privateIntegerage;//有参无参构造、get、set方法、toString()方法}3、思考,我们原来是如何给bean注入属性值的!@Value,给狗狗类测试一下:

@Component//注册beanpublicclassDog{@Value("阿黄")privateStringname;@Value("18")privateIntegerage;}4、在SpringBoot的测试类下注入狗狗输出一下;

@SpringBootTestclassDemoApplicationTests{@Autowired//将狗狗自动注入进来Dogdog;@TestpublicvoidcontextLoads(){System.out.println(dog);//打印看下狗狗对象}}结果成功输出,@Value注入成功,这是我们原来的办法对吧。

5、我们在编写一个复杂一点的实体类:Person类

@Component//注册bean到容器中publicclassPerson{privateStringname;privateIntegerage;privateBooleanhappy;privateDatebirth;privateMapmaps;privateListlists;privateDogdog;//有参无参构造、get、set方法、toString()方法}6、我们来使用yaml配置的方式进行注入,大家写的时候注意区别和优势,我们编写一个yaml配置!

person:name:qinjiangage:3happy:falsebirth:2000/01/01maps:{k1:v1,k2:v2}lists:-code-girl-musicdog:name:旺财age:17、我们刚才已经把person这个对象的所有值都写好了,我们现在来注入到我们的类中!

org.springframework.bootspring-boot-configuration-processortrue9、确认以上配置都OK之后,我们去测试类中测试一下:

@SpringBootTestclassDemoApplicationTests{@AutowiredPersonperson;//将person自动注入进来@TestpublicvoidcontextLoads(){System.out.println(person);//打印person信息}}结果:所有值全部注入成功!

yaml配置注入到实体类完全OK!

课堂测试:

1、将配置文件的key值和属性的值设置为不一样,则结果输出为null,注入失败

2、在配置一个person2,然后将@ConfigurationProperties(prefix="person2")指向我们的person2;

加载指定的配置文件

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

@configurationProperties:默认从全局配置文件中获取值;

1、我们去在resources目录下新建一个person.properties文件

name=kuangshen2、然后在我们的代码中指定加载person.properties文件

@PropertySource(value="classpath:person.properties")@Component//注册beanpublicclassPerson{@Value("${name}")privateStringname;......}3、再次输出测试一下:指定配置文件绑定成功!

配置文件占位符

配置文件还可以编写占位符生成随机数

person:name:qinjiang${random.uuid}#随机uuidage:${random.int}#随机inthappy:falsebirth:2000/01/01maps:{k1:v1,k2:v2}lists:-code-girl-musicdog:name:${person.hello:other}_旺财age:1回顾properties配置

我们上面采用的yaml方法都是最简单的方式,开发中最常用的;也是springboot所推荐的!那我们来唠唠其他的实现方式,道理都是相同的;写还是那样写;配置文件除了yml还有我们之前常用的properties,我们没有讲,我们来唠唠!

【注意】properties配置文件在写中文的时候,会有乱码,我们需要去IDEA中设置编码格式为UTF-8;

settings-->FileEncodings中配置;

测试步骤:

1、新建一个实体类User

@Component//注册beanpublicclassUser{privateStringname;privateintage;privateStringsex;}2、编辑配置文件user.properties

user1.name=kuangshenuser1.age=18user1.sex=男3、我们在User类上使用@Value来进行注入!

@Component//注册bean@PropertySource(value="classpath:user.properties")publicclassUser{//直接使用@value@Value("${user.name}")//从配置文件中取值privateStringname;@Value("#{9*2}")//#{SPEL}Spring表达式privateintage;@Value("男")//字面量privateStringsex;}4、Springboot测试

@SpringBootTestclassDemoApplicationTests{@AutowiredUseruser;@TestpublicvoidcontextLoads(){System.out.println(user);}}结果正常输出:

对比小结

@Value这个使用起来并不友好!我们需要为每个属性单独注解赋值,比较麻烦;我们来看个功能对比图

1、@ConfigurationProperties只需要写一次即可,@Value则需要每个字段都添加

2、松散绑定:这个什么意思呢比如我的yml中写的last-name,这个和lastName是一样的,-后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下

3、JSR303数据校验,这个就是我们可以在字段是增加一层过滤器验证,可以保证数据的合法性

4、复杂类型封装,yml中可以封装对象,使用value就不支持

配置yml和配置properties都可以获取到值,强烈推荐yml;

如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下@value;

如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!

先看看如何使用

Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;

使用数据校验,可以保证数据的正确性;

常见参数

多配置文件

我们在主配置文件编写的时候,文件名可以是application-{profile}.properties/yml,用来指定多个环境版本;

例如:

application-test.properties代表测试环境配置

application-dev.properties代表开发环境配置

但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件;

我们需要通过一个配置来选择需要激活的环境:

#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;spring.profiles.active=devyaml的多文档块

和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了!

server:port:8081#选择要激活那个环境块spring:profiles:active:prod---server:port:8083spring:profiles:dev#配置环境的名称---server:port:8084spring:profiles:prod#配置环境的名称注意:如果yml和properties同时都配置了端口,并且没有激活其他环境,默认会使用properties配置文件的!

配置文件加载位置

外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置!

官方外部配置文件说明参考文档

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

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

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

我们在最低级的配置文件中设置一个项目访问路径的配置来测试互补问题;

拓展,运维小技巧

指定位置加载配置文件

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

项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高

java-jarspring-boot-config.jar--spring.config.location=F:/application.properties6、自动配置原理配置文件到底能写什么?怎么写?

SpringBoot官方文档中有大量的配置,我们无法全部记住

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

这就是自动装配的原理!

精髓

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

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

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

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

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

了解:@Conditional

@Conditional派生注解(Spring注解版原生的@Conditional作用)

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。

我们怎么知道哪些自动配置类生效?

我们可以通过启用debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

#开启springboot的调试类debug=truePositivematches:(自动配置类启用的:正匹配)

Negativematches:(没有启动,没有匹配成功的自动配置类:负匹配)

Unconditionalclasses:(没有条件的类)

说明

启动器模块是一个空jar文件,仅提供辅助性依赖管理,这些依赖可能用于自动装配或者其他类库;

命名归约:

官方命名:

自定义命名:

1、在IDEA中新建一个空项目spring-boot-starter-diy

2、新建一个普通Maven模块:kuang-spring-boot-starter

3、新建一个Springboot模块:kuang-spring-boot-starter-autoconfigure

4、点击apply即可,基本结构

5、在我们的starter中导入autoconfigure的依赖!

com.kuangkuang-spring-boot-starter-autoconfigure0.0.1-SNAPSHOT6、将autoconfigure项目下多余的文件都删掉,Pom中只留下一个starter,这是所有的启动器基本配置!

7、我们编写一个自己的服务

packagecom.kuang;publicclassHelloService{HelloPropertieshelloProperties;publicHelloPropertiesgetHelloProperties(){returnhelloProperties;}publicvoidsetHelloProperties(HelloPropertieshelloProperties){this.helloProperties=helloProperties;}publicStringsayHello(Stringname){returnhelloProperties.getPrefix()+name+helloProperties.getSuffix();}}8、编写HelloProperties配置类

packagecom.kuang;importorg.springframework.boot.context.properties.ConfigurationProperties;//前缀kuang.hello@ConfigurationProperties(prefix="kuang.hello")publicclassHelloProperties{privateStringprefix;privateStringsuffix;publicStringgetPrefix(){returnprefix;}publicvoidsetPrefix(Stringprefix){this.prefix=prefix;}publicStringgetSuffix(){returnsuffix;}publicvoidsetSuffix(Stringsuffix){this.suffix=suffix;}}9、编写我们的自动配置类并注入bean,测试!

packagecom.kuang;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;importorg.springframework.boot.context.properties.EnableConfigurationProperties;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@Configuration@ConditionalOnWebApplication//web应用生效@EnableConfigurationProperties(HelloProperties.class)publicclassHelloServiceAutoConfiguration{@AutowiredHelloPropertieshelloProperties;@BeanpublicHelloServicehelloService(){HelloServiceservice=newHelloService();service.setHelloProperties(helloProperties);returnservice;}}10、在resources编写一个自己的META-INF\spring.factories

#AutoConfigureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.kuang.HelloServiceAutoConfiguration11、编写完成后,可以安装到maven仓库中!

1、新建一个SpringBoot项目

2、导入我们自己写的启动器

com.kuangkuang-spring-boot-starter1.0-SNAPSHOT3、编写一个HelloController进行测试我们自己的写的接口!

packagecom.kuang.controller;@RestControllerpublicclassHelloController{@AutowiredHelloServicehelloService;@RequestMapping("/hello")publicStringhello(){returnhelloService.sayHello("zxc");}}4、编写配置文件application.properties

kuang.hello.prefix="ppp"kuang.hello.suffix="sss"5、启动项目进行测试,结果成功!

对于数据访问层,无论是SQL(关系型数据库)还是NOSQL(非关系型数据库),SpringBoot底层都是采用SpringData的方式进行统一处理。

SpringBoot底层都是采用SpringData的方式进行统一处理各种数据库,SpringData也是Spring中与SpringBoot、SpringCloud等齐名的知名项目。

创建测试项目测试数据源

1、我去新建一个项目测试:springboot-data-jdbc;引入相应的模块!基础模块

2、项目建好之后,发现自动帮我们导入了如下的启动器:

org.springframework.bootspring-boot-starter-jdbcmysqlmysql-connector-javaruntime3、编写yaml配置文件连接数据库;

spring:datasource:username:rootpassword:123456#serverTimezone=UTC解决时区的报错url:jdbc:mysql://localhost:3306/springbootserverTimezone=UTC&useUnicode=true&characterEncoding=utf-8driver-class-name:com.mysql.cj.jdbc.Driver4、配置完这一些东西后,我们就可以直接去使用了,因为SpringBoot已经默认帮我们进行了自动配置;去测试类测试一下

@SpringBootTestclassSpringbootDataJdbcApplicationTests{//DI注入数据源@AutowiredDataSourcedataSource;@TestpublicvoidcontextLoads()throwsSQLException{//看一下默认数据源System.out.println(dataSource.getClass());//获得连接Connectionconnection=dataSource.getConnection();System.out.println(connection);//关闭连接connection.close();}}结果:我们可以看到他默认给我们配置的数据源为:classcom.zaxxer.hikari.HikariDataSource,我们并没有手动配置

我们来全局搜索一下,找到数据源的所有自动配置都在:DataSourceAutoConfiguration文件:

@Import({Hikari.class,Tomcat.class,Dbcp2.class,Generic.class,DataSourceJmxConfiguration.class})protectedstaticclassPooledDataSourceConfiguration{protectedPooledDataSourceConfiguration(){}}这里导入的类都在DataSourceConfiguration配置类下,可以看出SpringBoot2.2.5默认使用HikariDataSource数据源,而以前版本,如SpringBoot1.5默认使用org.apache.tomcat.jdbc.pool.DataSource作为数据源;

HikariDataSource号称JavaWEB当前速度最快的数据源,相比于传统的C3P0、DBCP、Tomcatjdbc等连接池更加优秀;

可以使用spring.datasource.type指定自定义的数据源类型,值为要使用的连接池实现的完全限定名。

关于数据源我们并不做介绍,有了数据库连接,显然就可以CRUD操作数据库了。但是我们需要先了解一个对象JdbcTemplate

JDBCTemplate

1、有了数据源(com.zaxxer.hikari.HikariDataSource),然后可以拿到数据库连接(java.sql.Connection),有了连接,就可以使用原生的JDBC语句来操作数据库;

2、即使不使用第三方第数据库操作框架,如MyBatis等,Spring本身也对原生的JDBC做了轻量级的封装,即JdbcTemplate。

3、数据库操作的所有CRUD方法都在JdbcTemplate中。

4、SpringBoot不仅提供了默认的数据源,同时默认已经配置好了JdbcTemplate放在了容器中,程序员只需自己注入即可使用

5、JdbcTemplate的自动配置是依赖org.springframework.boot.autoconfigure.jdbc包下的JdbcTemplateConfiguration类

JdbcTemplate主要提供以下几类方法:

测试

编写一个Controller,注入jdbcTemplate,编写测试方法进行访问测试;

到此,CURD的基本操作,使用JDBC就搞定了。

Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。

Druid是阿里巴巴开源平台上一个数据库连接池实现,结合了C3P0、DBCP等DB池的优点,同时加入了日志监控。

Druid可以很好的监控DB池连接和SQL的执行情况,天生就是针对监控而生的DB连接池。

Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。

SpringBoot2.0以上默认使用Hikari数据源,可以说Hikari与Driud都是当前JavaWeb上最优秀的数据源,我们来重点介绍SpringBoot如何集成Druid数据源,如何实现数据库监控。

com.alibaba.druid.pool.DruidDataSource基本配置参数如下:

1、添加上Druid数据源依赖。

spring:datasource:username:rootpassword:123456url:jdbc:mysql://localhost:3306/springbootserverTimezone=UTC&useUnicode=true&characterEncoding=utf-8driver-class-name:com.mysql.cj.jdbc.Drivertype:com.alibaba.druid.pool.DruidDataSource#自定义数据源3、数据源切换之后,在测试类中注入DataSource,然后获取到它,输出一看便知是否成功切换;

packagecom.kuang.config;importcom.alibaba.druid.pool.DruidDataSource;importorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importjavax.sql.DataSource;@ConfigurationpublicclassDruidConfig{/*将自定义的Druid数据源添加到容器中,不再让SpringBoot自动创建绑定全局配置文件中的druid数据源属性到com.alibaba.druid.pool.DruidDataSource从而让它们生效@ConfigurationProperties(prefix="spring.datasource"):作用就是将全局配置文件中前缀为spring.datasource的属性值注入到com.alibaba.druid.pool.DruidDataSource的同名参数中*/@ConfigurationProperties(prefix="spring.datasource")@BeanpublicDataSourcedruidDataSource(){returnnewDruidDataSource();}}7、去测试类中测试一下;看是否成功!

@SpringBootTestclassSpringbootDataJdbcApplicationTests{//DI注入数据源@AutowiredDataSourcedataSource;@TestpublicvoidcontextLoads()throwsSQLException{//看一下默认数据源System.out.println(dataSource.getClass());//获得连接Connectionconnection=dataSource.getConnection();System.out.println(connection);DruidDataSourcedruidDataSource=(DruidDataSource)dataSource;System.out.println("druidDataSource数据源最大连接数:"+druidDataSource.getMaxActive());System.out.println("druidDataSource数据源初始化连接数:"+druidDataSource.getInitialSize());//关闭连接connection.close();}}输出结果:可见配置参数已经生效!

Druid数据源具有监控的功能,并提供了一个web界面方便用户查看,类似安装路由器时,人家也提供了一个默认的web页面。

进入之后

配置Druidweb监控filter过滤器

1、导入MyBatis所需要的依赖

org.mybatis.spring.bootmybatis-spring-boot-starter2.1.12、配置数据库连接信息(不变)

4、创建实体类,导入Lombok!

Department.java

packagecom.kuang.pojo;importlombok.AllArgsConstructor;importlombok.Data;importlombok.NoArgsConstructor;@Data@NoArgsConstructor@AllArgsConstructorpublicclassDepartment{privateIntegerid;privateStringdepartmentName;}5、创建mapper目录以及对应的Mapper接口

DepartmentMapper.java

//@Mapper:表示本类是一个MyBatis的Mapper@Mapper@RepositorypublicinterfaceDepartmentMapper{//获取所有部门信息ListgetDepartments();//通过id获得部门DepartmentgetDepartment(Integerid);}6、对应的Mapper映射文件

DepartmentMapper.xml

src/main/java**/*.xmltrue8、编写部门的DepartmentController进行测试!

@RestControllerpublicclassDepartmentController{@AutowiredDepartmentMapperdepartmentMapper;//查询全部部门@GetMapping("/getDepartments")publicListgetDepartments(){returndepartmentMapper.getDepartments();}//查询全部部门@GetMapping("/getDepartment/{id}")publicDepartmentgetDepartment(@PathVariable("id")Integerid){returndepartmentMapper.getDepartment(id);}}启动项目访问进行测试!

我们增加一个员工类再测试下,为之后做准备

1、新建一个pojo类Employee;

@Data@AllArgsConstructor@NoArgsConstructorpublicclassEmployee{privateIntegerid;privateStringlastName;privateStringemail;//1male,0femaleprivateIntegergender;privateIntegerdepartment;privateDatebirth;privateDepartmenteDepartment;//冗余设计}2、新建一个EmployeeMapper接口

//@Mapper:表示本类是一个MyBatis的Mapper@Mapper@RepositorypublicinterfaceEmployeeMapper{//获取所有员工信息ListgetEmployees();//新增一个员工intsave(Employeeemployee);//通过id获得员工信息Employeeget(Integerid);//通过id删除员工intdelete(Integerid);}3、编写EmployeeMapper.xml配置文件

@RestControllerpublicclassEmployeeController{@AutowiredEmployeeMapperemployeeMapper;//获取所有员工信息@GetMapping("/getEmployees")publicListgetEmployees(){returnemployeeMapper.getEmployees();}@GetMapping("/save")publicintsave(){Employeeemployee=newEmployee();employee.setLastName("kuangshen");employee.setEmail("qinjiang@qq.com");employee.setGender(1);employee.setDepartment(101);employee.setBirth(newDate());returnemployeeMapper.save(employee);}//通过id获得员工信息@GetMapping("/get/{id}")publicEmployeeget(@PathVariable("id")Integerid){returnemployeeMapper.get(id);}//通过id删除员工@GetMapping("/delete/{id}")publicintdelete(@PathVariable("id")Integerid){returnemployeeMapper.delete(id);}}11、Web开发静态资源处理11.1、Web开发探究简介

使用SpringBoot的步骤:

1、创建一个SpringBoot应用,选择我们需要的模块,SpringBoot就会默认将我们的需要的模块自动配置好

2、手动在配置文件中配置部分配置项目就可以运行起来了

3、专注编写业务代码,不需要考虑以前那样一大堆的配置了。

要熟悉掌握开发,之前学习的自动配置的原理一定要搞明白!

比如SpringBoot到底帮我们配置了什么?我们能不能修改?我们能修改哪些配置?我们能不能扩展?

首先,我们搭建一个普通的SpringBoot项目,回顾一下HelloWorld程序!

写请求非常简单,那我们要引入我们前端资源,我们项目中有许多的静态资源,比如css,js等文件,这个SpringBoot怎么处理呢?

如果我们是一个web应用,我们的main下会有一个webapp,我们以前都是将所有的页面导在这里面的,对吧!但是我们现在的pom呢,打包方式是为jar的方式,那么这种方式SpringBoot能不能来给我们写页面呢?当然是可以的,但是SpringBoot对于静态资源放置的位置,是有规定的!

我们先来聊聊这个静态资源映射规则:

SpringBoot中,SpringMVC的web配置都在WebMvcAutoConfiguration这个配置类里面;

我们可以去看看WebMvcAutoConfigurationAdapter中有很多配置方法;

有一个方法:addResourceHandlers添加资源处理

什么是webjars呢?

Webjars本质就是以jar包的方式引入我们的静态资源,我们以前要导入一个静态资源文件,直接导入即可。

使用SpringBoot需要使用Webjars,我们可以去搜索一下:

要使用jQuery,我们只要要引入jQuery对应版本的pom依赖即可!

org.webjarsjquery3.4.1导入完毕,查看webjars目录结构,并访问Jquery.js文件!

第二种静态资源映射规则

那我们项目中要是使用自己的静态资源该怎么导入呢?我们看下一行代码;

我们去找staticPathPattern发现第二种映射规则:/**,访问当前的项目任意资源,它会去找resourceProperties这个类,我们可以点进去看一下分析:

//进入方法publicString[]getStaticLocations(){returnthis.staticLocations;}//找到对应的值privateString[]staticLocations=CLASSPATH_RESOURCE_LOCATIONS;//找到路径privatestaticfinalString[]CLASSPATH_RESOURCE_LOCATIONS={"classpath:/META-INF/resources/","classpath:/resources/","classpath:/static/","classpath:/public/"};ResourceProperties可以设置和我们静态资源有关的参数;这里面指向了它会去寻找资源的文件夹,即上面数组的内容。

所以得出结论,以下四个目录存放的静态资源可以被我们识别:

"classpath:/META-INF/resources/""classpath:/resources/""classpath:/static/""classpath:/public/"我们可以在resources根目录下新建对应的文件夹,都可以存放我们的静态文件;

自定义静态资源路径

我们也可以自己通过配置文件来指定一下,哪些文件夹是需要我们放静态资源文件的,在application.properties中配置;

spring.resources.static-locations=classpath:/coding/,classpath:/kuang/一旦自己定义了静态文件夹的路径,原来的自动配置就都会失效了!

静态资源文件夹说完后,我们继续向下看源码!可以看到一个欢迎页的映射,就是我们的首页!

@BeanpublicWelcomePageHandlerMappingwelcomePageHandlerMapping(ApplicationContextapplicationContext,FormattingConversionServicemvcConversionService,ResourceUrlProvidermvcResourceUrlProvider){WelcomePageHandlerMappingwelcomePageHandlerMapping=newWelcomePageHandlerMapping(newTemplateAvailabilityProviders(applicationContext),applicationContext,getWelcomePage(),//getWelcomePage获得欢迎页this.mvcProperties.getStaticPathPattern());welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService,mvcResourceUrlProvider));returnwelcomePageHandlerMapping;}点进去继续看

privateOptionalgetWelcomePage(){String[]locations=getResourceLocations(this.resourceProperties.getStaticLocations());//::是java8中新引入的运算符//Class::function的时候function是属于Class的,应该是静态方法。//this::function的funtion是属于这个对象的。//简而言之,就是一种语法糖而已,是一种简写returnArrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();}//欢迎页就是一个location下的的index.html而已privateResourcegetIndexHtml(Stringlocation){returnthis.resourceLoader.getResource(location+"index.html");}欢迎页,静态资源文件夹下的所有index.html页面;被/**映射。

关于网站图标说明:

与其他静态资源一样,SpringBoot在配置的静态内容位置中查找favicon.ico。如果存在这样的文件,它将自动用作应用程序的favicon。

1、关闭SpringBoot默认图标

#关闭默认图标spring.mvc.favicon.enabled=false2、自己放一个图标在静态资源目录下,我放在public目录下

3、清除浏览器缓存!刷新网页,发现图标已经变成自己的了!

前端交给我们的页面,是html页面。如果是我们以前开发,我们需要把他们转成jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示,及交互等。

jsp支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,SpringBoot这个项目首先是以jar的方式,不是war,像第二,我们用的还是嵌入式的Tomcat,所以呢,他现在默认是不支持jsp的。

那不支持jsp,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦,那怎么办呢?

SpringBoot推荐你可以来使用模板引擎:

模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢我们来看一下这张图:

模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。只不过呢,就是说不同模板引擎之间,他们可能这个语法有点不一样。其他的我就不介绍了,我主要来介绍一下SpringBoot给我们推荐的Thymeleaf模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。而且呢,功能更强大。

我们呢,就来看一下这个模板引擎,那既然要看这个模板引擎。首先,我们来看SpringBoot里边怎么用。

引入Thymeleaf

怎么引入呢,对于springboot来说,什么事情不都是一个start的事情嘛,我们去在项目中引入一下。给大家三个网址:

Spring官方文档:找到我们对应的版本

找到对应的pom依赖:可以适当点进源码看下本来的包!

org.springframework.bootspring-boot-starter-thymeleafMaven会自动下载jar包,我们可以去看下下载的东西;

前面呢,我们已经引入了Thymeleaf,那这个要怎么使用呢?

我们首先得按照SpringBoot的自动配置原理看一下我们这个Thymeleaf的自动配置规则,在按照那个规则,我们进行使用。

我们去找一下Thymeleaf的自动配置类:ThymeleafProperties

@ConfigurationProperties(prefix="spring.thymeleaf")publicclassThymeleafProperties{privatestaticfinalCharsetDEFAULT_ENCODING;publicstaticfinalStringDEFAULT_PREFIX="classpath:/templates/";publicstaticfinalStringDEFAULT_SUFFIX=".html";privatebooleancheckTemplate=true;privatebooleancheckTemplateLocation=true;privateStringprefix="classpath:/templates/";privateStringsuffix=".html";privateStringmode="HTML";privateCharsetencoding;}我们可以在其中看到默认的前缀和后缀!

我们只需要把我们的html页面放在类路径下的templates下,thymeleaf就可以帮我们自动渲染了。

使用thymeleaf什么都不需要配置,只需要将他放在指定的文件夹下即可!

1、编写一个TestController

@ControllerpublicclassTestController{@RequestMapping("/t1")publicStringtest1(){//classpath:/templates/test.htmlreturn"test";}}2、编写一个测试页面test.html放在templates目录下

Title

测试页面

3、启动项目请求测试

要学习语法,还是参考官网文档最为准确,我们找到对应的版本看一下;

我们做个最简单的练习:我们需要查出一些数据,在页面中展示

1、修改测试请求,增加数据传输;

@RequestMapping("/t1")publicStringtest1(Modelmodel){//存入数据model.addAttribute("msg","Hello,Thymeleaf");//classpath:/templates/test.htmlreturn"test";}2、我们要使用thymeleaf,需要在html文件中导入命名空间的约束,方便提示。

我们可以去官方文档的#3中看一下命名空间拿来过来:

OK,入门搞定,我们来认真研习一下Thymeleaf的使用语法!

1、我们可以使用任意的th:attr来替换Html中原生属性的值!

2、我们能写哪些表达式呢?

1、我们编写一个Controller,放一些数据

@RequestMapping("/t2")publicStringtest2(Mapmap){//存入数据map.put("msg","

Hello

");map.put("users",Arrays.asList("qinjiang","kuangshen"));//classpath:/templates/test.htmlreturn"test";}2、测试页面取出数据

官网阅读

在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制。

只有把这些都搞清楚了,我们在之后使用才会更加得心应手。途径一:源码分析,途径二:官方文档!

自动配置了ViewResolver,就是我们之前学习的SpringMVC的视图解析器;

即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。

我们去看看这里的源码:我们找到WebMvcAutoConfiguration,然后搜索ContentNegotiatingViewResolver。找到如下方法!

@Bean@ConditionalOnBean(ViewResolver.class)@ConditionalOnMissingBean(name="viewResolver",value=ContentNegotiatingViewResolver.class)publicContentNegotiatingViewResolverviewResolver(BeanFactorybeanFactory){ContentNegotiatingViewResolverresolver=newContentNegotiatingViewResolver();resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));//ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);returnresolver;}我们可以点进这类看看!找到对应的解析视图的代码;

@Nullable//注解说明:@Nullable即参数可为nullpublicViewresolveViewName(StringviewName,Localelocale)throwsException{RequestAttributesattrs=RequestContextHolder.getRequestAttributes();Assert.state(attrsinstanceofServletRequestAttributes,"NocurrentServletRequestAttributes");ListrequestedMediaTypes=this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());if(requestedMediaTypes!=null){//获取候选的视图对象ListcandidateViews=this.getCandidateViews(viewName,locale,requestedMediaTypes);//选择一个最适合的视图对象,然后把这个对象返回ViewbestView=this.getBestView(candidateViews,requestedMediaTypes,attrs);if(bestView!=null){returnbestView;}}//.....}我们继续点进去看,他是怎么获得候选的视图的呢?

getCandidateViews中看到他是把所有的视图解析器拿来,进行while循环,挨个解析!

Iteratorvar5=this.viewResolvers.iterator();所以得出结论:ContentNegotiatingViewResolver这个视图解析器就是用来组合所有的视图解析器的

我们再去研究下他的组合逻辑,看到有个属性viewResolvers,看看它是在哪里进行赋值的!

protectedvoidinitServletContext(ServletContextservletContext){//这里它是从beanFactory工具中获取容器中的所有视图解析器//ViewRescolver.class把所有的视图解析器来组合的CollectionmatchingBeans=BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(),ViewResolver.class).values();ViewResolverviewResolver;if(this.viewResolvers==null){this.viewResolvers=newArrayList(matchingBeans.size());}//...............}既然它是在容器中去找视图解析器,我们是否可以猜想,我们就可以去实现一个视图解析器了呢?

我们可以自己给容器中去添加一个视图解析器;这个类就会帮我们自动的将它组合进来;我们去实现一下

1、我们在我们的主程序中去写一个视图解析器来试试;

@Bean//放到bean中publicViewResolvermyViewResolver(){returnnewMyViewResolver();}//我们写一个静态内部类,视图解析器就需要实现ViewResolver接口privatestaticclassMyViewResolverimplementsViewResolver{@OverridepublicViewresolveViewName(Strings,Localelocale)throwsException{returnnull;}}2、怎么看我们自己写的视图解析器有没有起作用呢?

我们给DispatcherServlet中的doDispatch方法加个断点进行调试一下,因为所有的请求都会走到这个方法中

3、我们启动我们的项目,然后随便访问一个页面,看一下Debug信息;

找到this

找到视图解析器,我们看到我们自己定义的就在这里了;

所以说,我们如果想要使用自己定制化的东西,我们只需要给容器中添加这个组件就好了!剩下的事情SpringBoot就会帮我们做了!

转换器和格式化器

找到格式化转换器:

@Bean@OverridepublicFormattingConversionServicemvcConversionService(){//拿到配置文件中的格式化规则WebConversionServiceconversionService=newWebConversionService(this.mvcProperties.getDateFormat());addFormatters(conversionService);returnconversionService;}点击去:

publicStringgetDateFormat(){returnthis.dateFormat;}/***Dateformattouse.Forinstance,`dd/MM/yyyy`.默认的*/privateStringdateFormat;可以看到在我们的Properties文件中,我们可以进行自动配置它!

如果配置了自己的格式化方式,就会注册到Bean中生效,我们可以在配置文件中配置日期格式化的规则:

其余的就不一一举例了,大家可以下去多研究探讨即可!

这么多的自动配置,原理都是一样的,通过这个WebMVC的自动配置原理分析,我们要学会一种学习方式,通过源码探究,得出结论;这个结论一定是属于自己的,而且一通百通。

SpringBoot的底层,大量用到了这些设计细节思想,所以,没事需要多阅读源码!得出结论;

SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;

如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!

扩展使用SpringMVC官方文档如下:

IfyouwanttokeepSpringBootMVCfeaturesandyouwanttoaddadditionalMVCconfiguration(interceptors,formatters,viewcontrollers,andotherfeatures),youcanaddyourown@ConfigurationclassoftypeWebMvcConfigurerbutwithout@EnableWebMvc.IfyouwishtoprovidecustominstancesofRequestMappingHandlerMapping,RequestMappingHandlerAdapter,orExceptionHandlerExceptionResolver,youcandeclareaWebMvcRegistrationsAdapterinstancetoprovidesuchcomponents.

我们要做的就是编写一个@Configuration注解类,并且类型要为WebMvcConfigurer,还不能标注@EnableWebMvc注解;我们去自己写一个;我们新建一个包叫config,写一个类MyMvcConfig;

//应为类型要求为WebMvcConfigurer,所以我们实现其接口//可以使用自定义类扩展MVC的功能@ConfigurationpublicclassMyMvcConfigimplementsWebMvcConfigurer{@OverridepublicvoidaddViewControllers(ViewControllerRegistryregistry){//浏览器发送/test,就会跳转到test页面;registry.addViewController("/test").setViewName("test");}}我们去浏览器访问一下:

确实也跳转过来了!所以说,我们要扩展SpringMVC,官方就推荐我们这么去使用,既保SpringBoot留所有的自动配置,也能用我们扩展的配置!

我们可以去分析一下原理:

1、WebMvcAutoConfiguration是SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter

2、这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)

3、我们点进EnableWebMvcConfiguration这个类看一下,它继承了一个父类:DelegatingWebMvcConfiguration

这个父类中有这样一段代码:

publicclassDelegatingWebMvcConfigurationextendsWebMvcConfigurationSupport{privatefinalWebMvcConfigurerCompositeconfigurers=newWebMvcConfigurerComposite();//从容器中获取所有的webmvcConfigurer@Autowired(required=false)publicvoidsetConfigurers(Listconfigurers){if(!CollectionUtils.isEmpty(configurers)){this.configurers.addWebMvcConfigurers(configurers);}}}4、我们可以在这个类中去寻找一个我们刚才设置的viewController当做参考,发现它调用了一个

protectedvoidaddViewControllers(ViewControllerRegistryregistry){this.configurers.addViewControllers(registry);}5、我们点进去看一下

官方文档:

IfyouwanttotakecompletecontrolofSpringMVCyoucanaddyourown@Configurationannotatedwith@EnableWebMvc.全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置!

只需在我们的配置类中要加一个@EnableWebMvc。

我们看下如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效,我们可以去测试一下;

不加注解之前,访问首页:

给配置类加上注解:@EnableWebMvc

我们发现所有的SpringMVC自动配置都失效了!回归到了最初的样子;

当然,我们开发中,不推荐使用全面接管SpringMVC

思考问题?为什么加了一个注解,自动配置就失效了!我们看下源码:

1、这里发现它是导入了一个类,我们可以继续进去看

@Import({DelegatingWebMvcConfiguration.class})public@interfaceEnableWebMvc{}2、它继承了一个父类WebMvcConfigurationSupport

publicclassDelegatingWebMvcConfigurationextendsWebMvcConfigurationSupport{//......}3、我们来回顾一下Webmvc自动配置类

@Configuration(proxyBeanMethods=false)@ConditionalOnWebApplication(type=Type.SERVLET)@ConditionalOnClass({Servlet.class,DispatcherServlet.class,WebMvcConfigurer.class})//这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE+10)@AutoConfigureAfter({DispatcherServletAutoConfiguration.class,TaskExecutionAutoConfiguration.class,ValidationAutoConfiguration.class})publicclassWebMvcAutoConfiguration{}总结一句话:@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了;

而导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能!

在SpringBoot中会有非常多的扩展配置,只要看见了这个,我们就应该多留心注意~

先在IDEA中统一设置properties的编码问题!

1、我们在resources资源文件下新建一个i18n目录,存放国际化配置文件

2、建立一个login.properties文件,还有一个login_zh_CN.properties;发现IDEA自动识别了我们要做国际化操作;文件夹变了!

3、我们可以在这上面去新建一个文件;

弹出如下页面:我们再添加一个英文的;

这样就快捷多了!

4、接下来,我们就来编写配置,我们可以看到idea下面有另外一个视图;

这个视图我们点击+号就可以直接添加属性了;我们新建一个login.tip,可以看到边上有三个文件框可以输入

我们添加一下首页的内容!

然后依次添加其他页面内容即可!

然后去查看我们的配置文件;

login.properties:默认

login.btn=Signinlogin.password=Passwordlogin.remember=Remembermelogin.tip=Pleasesigninlogin.username=Username中文:

我们去看一下SpringBoot对国际化的自动配置!这里又涉及到一个类:MessageSourceAutoConfiguration

里面有一个方法,这里发现SpringBoot已经自动配置好了管理我们国际化资源文件的组件ResourceBundleMessageSource;

//获取properties传递过来的值进行判断@BeanpublicMessageSourcemessageSource(MessageSourcePropertiesproperties){ResourceBundleMessageSourcemessageSource=newResourceBundleMessageSource();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());DurationcacheDuration=properties.getCacheDuration();if(cacheDuration!=null){messageSource.setCacheMillis(cacheDuration.toMillis());}messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());returnmessageSource;}我们真实的情况是放在了i18n目录下,所以我们要去配置这个messages的路径;

spring.messages.basename=i18n.login14.4、配置页面国际化值去页面获取国际化的值,查看Thymeleaf的文档,找到message取值操作为:#{...}。我们去页面测试下:

IDEA还有提示,非常智能的!

我们可以去启动项目,访问一下,发现已经自动识别为中文的了!

但是我们想要更好!可以根据按钮自动切换中文英文!

在Spring中有一个国际化的Locale(区域信息对象);里面有一个叫做LocaleResolver(获取区域信息对象)的解析器!

我们去我们webmvc自动配置文件,寻找一下!看到SpringBoot默认配置:

@Bean@ConditionalOnMissingBean@ConditionalOnProperty(prefix="spring.mvc",name="locale")publicLocaleResolverlocaleResolver(){//容器中没有就自己配,有的话就用用户配置的if(this.mvcProperties.getLocaleResolver()==WebMvcProperties.LocaleResolver.FIXED){returnnewFixedLocaleResolver(this.mvcProperties.getLocale());}//接收头国际化分解AcceptHeaderLocaleResolverlocaleResolver=newAcceptHeaderLocaleResolver();localeResolver.setDefaultLocale(this.mvcProperties.getLocale());returnlocaleResolver;}AcceptHeaderLocaleResolver这个类中有一个方法

publicLocaleresolveLocale(HttpServletRequestrequest){LocaledefaultLocale=this.getDefaultLocale();//默认的就是根据请求头带来的区域信息获取Locale进行国际化if(defaultLocale!=null&&request.getHeader("Accept-Language")==null){returndefaultLocale;}else{LocalerequestLocale=request.getLocale();ListsupportedLocales=this.getSupportedLocales();if(!supportedLocales.isEmpty()&&!supportedLocales.contains(requestLocale)){LocalesupportedLocale=this.findSupportedLocale(request,supportedLocales);if(supportedLocale!=null){returnsupportedLocale;}else{returndefaultLocale!=nulldefaultLocale:requestLocale;}}else{returnrequestLocale;}}}那假如我们现在想点击链接让我们的国际化资源生效,就需要让我们自己的Locale生效!

我们去自己写一个自己的LocaleResolver,可以在链接上携带区域信息!

修改一下前端页面的跳转连接:

学习目标:

前后端分离

产生的问题

解决方案

Swagger

SpringBoot集成Swagger=>springfox,两个jar包

使用Swagger

要求:jdk1.8+否则swagger2无法运行

步骤:

1、新建一个SpringBoot-web项目

2、添加Maven依赖

4、要使用Swagger,我们需要编写一个配置类-SwaggerConfig来配置Swagger

1、Swagger实例Bean是Docket,所以通过配置Docket实例来配置Swaggger。

@Bean//配置docket以配置Swagger具体参数publicDocketdocket(){returnnewDocket(DocumentationType.SWAGGER_2);}2、可以通过apiInfo()属性配置文档信息

1、构建Docket时通过select()方法配置怎么扫描接口。

@BeanpublicDocketdocket(){returnnewDocket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()//通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口.apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller")).build();}2、重启项目测试,由于我们配置根据包的路径扫描接口,所以我们只能看到一个类

3、除了通过包路径配置扫描接口外,还可以通过配置其他方式扫描接口,这里注释一下所有的配置方式:

any()//扫描所有,项目中的所有接口都会被扫描到none()//不扫描接口//通过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求withMethodAnnotation(finalClassannotation)//通过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口withClassAnnotation(finalClassannotation)basePackage(finalStringbasePackage)//根据包路径扫描接口4、除此之外,我们还可以配置接口扫描过滤:

@BeanpublicDocketdocket(){returnnewDocket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()//通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口.apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))//配置如何通过path过滤,即这里只扫描请求以/kuang开头的接口.paths(PathSelectors.ant("/kuang/**")).build();}5、这里的可选值还有

1、通过enable()方法配置是否启用swagger,如果是false,swagger将不能在浏览器中访问了

@BeanpublicDocketdocket(){returnnewDocket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).enable(false)//配置是否启用Swagger,如果是false,在浏览器将无法访问.select()//通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口.apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))//配置如何通过path过滤,即这里只扫描请求以/kuang开头的接口.paths(PathSelectors.ant("/kuang/**")).build();}2、如何动态配置当项目处于test、dev环境时显示swagger,处于prod时不显示?

@BeanpublicDocketdocket(Environmentenvironment){//设置要显示swagger的环境Profilesof=Profiles.of("dev","test");//判断当前是否处于该环境//通过enable()接收此参数判断是否要显示booleanb=environment.acceptsProfiles(of);returnnewDocket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).enable(b)//配置是否启用Swagger,如果是false,在浏览器将无法访问.select()//通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口.apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))//配置如何通过path过滤,即这里只扫描请求以/kuang开头的接口.paths(PathSelectors.ant("/kuang/**")).build();}3、可以在项目中增加一个dev的配置文件查看效果!

1、如果没有配置分组,默认是default。通过groupName()方法即可配置分组:

@BeanpublicDocketdocket(Environmentenvironment){returnnewDocket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).groupName("hello")//配置分组//省略配置....}2、重启项目查看分组

3、如何配置多个分组?配置多个分组只需要配置多个docket即可:

@BeanpublicDocketdocket1(){returnnewDocket(DocumentationType.SWAGGER_2).groupName("group1");}@BeanpublicDocketdocket2(){returnnewDocket(DocumentationType.SWAGGER_2).groupName("group2");}@BeanpublicDocketdocket3(){returnnewDocket(DocumentationType.SWAGGER_2).groupName("group3");}4、重启项目查看即可

1、新建一个实体类

@ApiModel("用户实体")publicclassUser{@ApiModelProperty("用户名")publicStringusername;@ApiModelProperty("密码")publicStringpassword;}2、只要这个实体在请求接口的返回值上(即使是泛型),都能映射到实体项中:

@RequestMapping("/getUser")publicUsergetUser(){returnnewUser();}3、重启查看测试

注:并不是因为@ApiModel这个注解让实体显示在这里了,而是只要出现在接口方法的返回值上的实体都会显示在这里,而@ApiModel和@ApiModelProperty这两个注解只是为实体添加注释的。

@ApiModel为类添加注释

@ApiModelProperty为类属性添加注释

Swagger的所有注解定义在io.swagger.annotations包下

下面列一些经常用到的,未列举出来的可以另行查阅说明:

我们也可以给请求的接口配置一些注释

@ApiOperation("狂神的接口")@PostMapping("/kuang")@ResponseBodypublicStringkuang(@ApiParam("这个名字会被返回")Stringusername){returnusername;}这样的话,可以给一些比较难理解的属性或者接口,增加一些配置信息,让人更容易阅读!

相较于传统的Postman或Curl方式测试接口,使用swagger简直就是傻瓜式操作,不需要额外说明文档(写得好本身就是文档)而且更不容易出错,只需要录入数据然后点击Execute,如果再配合自动化框架,可以说基本就不需要人为操作了。

Swagger是个优秀的工具,现在国内已经有很多的中小型互联网公司都在使用它,相较于传统的要先出Word接口文档再测试的方式,显然这样也更符合现在的快速迭代开发行情。当然了,提醒下大家在正式环境要记得关闭Swagger,一来出于安全考虑二来也可以节省运行时内存。

1、创建一个service包

2、创建一个类AsyncService

异步处理还是非常常用的,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。

编写方法,假装正在处理数据,使用线程设置一些延时,模拟同步等待的情况;

@ServicepublicclassAsyncService{publicvoidhello(){try{Thread.sleep(3000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("业务进行中....");}}3、编写controller包

4、编写AsyncController类

我们去写一个Controller测试一下

问题:我们如果想让用户直接得到消息,就在后台使用多线程的方式进行处理即可,但是每次都需要自己手动去编写多线程的实现的话,太麻烦了,我们只需要用一个简单的办法,在我们的方法上加一个简单的注解即可,如下:

6、给hello方法添加@Async注解;

//告诉Spring这是一个异步方法@Asyncpublicvoidhello(){try{Thread.sleep(3000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("业务进行中....");}SpringBoot就会自己开一个线程池,进行调用!但是要让这个注解生效,我们还需要在主程序上添加一个注解@EnableAsync,开启异步注解功能;

@EnableAsync//开启异步注解功能@SpringBootApplicationpublicclassSpringbootTaskApplication{publicstaticvoidmain(String[]args){SpringApplication.run(SpringbootTaskApplication.class,args);}}7、重启测试,网页瞬间响应,后台代码依旧执行!

项目开发中经常需要执行一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息,Spring为我们提供了异步执行任务调度的方式,提供了两个接口。

两个注解:

cron表达式:

一、结构

corn从左到右(用空格隔开):秒分小时月份中的日期月份星期中的日期年份

二、各字段的含义

每一个域都使用数字,但还可以出现如下特殊字符,它们的含义是:

(1):表示匹配该域的任意值。假如在Minutes域使用,即表示每分钟都会触发事件。

(2):只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法:13131520*,其中最后一位只能用?,而不能使用,如果使用表示不管星期几都会触发,实际上并不是这样。

(3)-:表示范围。例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次

(5),:表示列出枚举值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。

(6)L:表示最后,只能出现在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。

(7)W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份。

(8)LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。

(9)#:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。

三、常用表达式例子

(1)0021**表示在每月的1日的凌晨2点调整任务

(2)01510*MON-FRI表示周一到周五每天上午10:15执行作业

(3)015106L2002-2006表示2002-2006年的每个月的最后一个星期五上午10:15执行作

(4)0010,14,16**每天上午10点,下午2点,4点

(6)0012*WED表示每个星期三中午12点

(7)0012**每天中午12点触发

(8)01510**每天上午10:15触发

(9)01510**每天上午10:15触发

(10)01510***每天上午10:15触发

(11)01510**20052005年的每天上午10:15触发

(12)0*14**在每天下午2点到下午2:59期间的每1分钟触发

(13)00/514**在每天下午2点到下午2:55期间的每5分钟触发

(14)00/514,18**在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发

(15)00-514**在每天下午2点到下午2:05期间的每1分钟触发

(16)010,44143WED每年三月的星期三的下午2:10和2:44触发

(17)01510*MON-FRI周一至周五的上午10:15触发

(18)0151015*每月15日上午10:15触发

(19)01510L*每月最后一日的上午10:15触发

(20)01510*6L每月的最后一个星期五上午10:15触发

(21)01510*6L2002-20052002年至2005年的每月的最后一个星期五上午10:15触发

(22)01510*6#3每月的第三个星期五上午10:15触发

1、创建一个ScheduledService

我们里面存在一个hello方法,他需要定时执行,怎么处理呢?

@ServicepublicclassScheduledService{//秒分时日月周几//0****MON-FRI//注意cron表达式的用法;@Scheduled(cron="0****0-7")publicvoidhello(){System.out.println("hello.....");}}2、这里写完定时任务之后,我们需要在主程序上增加@EnableScheduling开启定时任务功能

@EnableAsync//开启异步注解功能@EnableScheduling//开启基于注解的定时任务@SpringBootApplicationpublicclassSpringbootTaskApplication{publicstaticvoidmain(String[]args){SpringApplication.run(SpringbootTaskApplication.class,args);}}16.3、邮件任务邮件发送,在我们的日常开发中,也非常的多,Springboot也帮我们做了支持

测试:

1、引入pom依赖

org.springframework.bootspring-boot-starter-mail看它引入的依赖,可以看到jakarta.mail

com.sun.mailjakarta.mail1.6.4compile2、查看自动配置类:MailSenderAutoConfiguration

这个类中存在bean,JavaMailSenderImpl

然后我们去看下配置文件

@ConfigurationProperties(prefix="spring.mail")publicclassMailProperties{privatestaticfinalCharsetDEFAULT_CHARSET;privateStringhost;privateIntegerport;privateStringusername;privateStringpassword;privateStringprotocol="smtp";privateCharsetdefaultEncoding;privateMapproperties;privateStringjndiName;}3、配置文件:

4、Spring单元测试

我们只需要使用Thymeleaf进行前后端结合即可开发自己网站邮件收发功能了!

思考:我们平时在博客园,或者CSDN等平台进行写作的时候,有同学思考过他们的编辑器是怎么实现的吗?

在博客园后台的选项设置中,可以看到一个文本编辑器的选项:

其实这个就是富文本编辑器,市面上有许多非常成熟的富文本编辑器,比如:

Editor.md——功能非常丰富的编辑器,左端编辑,右端预览,非常方便,完全免费

wangEditor——基于javascript和css开发的Web富文本编辑器,轻量、简洁、界面美观、易用、开源免费。

TinyMCE——TinyMCE是一个轻量级的基于浏览器的所见即所得编辑器,由JavaScript写成。它对IE6+和Firefox1.5+都有着非常良好的支持。功能齐全,界面美观,就是文档是英文的,对开发人员英文水平有一定要求。

百度ueditor——UEditor是由百度web前端研发部开发所见即所得富文本web编辑器,具有轻量,功能齐全,可定制,注重用户体验等特点,开源基于MIT协议,允许自由使用和修改代码,缺点是已经没有更新了

kindeditor——界面经典。

Textbox——Textbox是一款极简但功能强大的在线文本编辑器,支持桌面设备和移动设备。主要功能包含内置的图像处理和存储、文件拖放、拼写检查和自动更正。此外,该工具还实现了屏幕阅读器等辅助技术,并符合WAI-ARIA可访问性标准。

CKEditor——国外的,界面美观。

quill——功能强大,还可以编辑公式等

simditor——界面美观,功能较全。

summernote——UI好看,精美

jodit——功能齐全

froalaEditor——界面非常好看,功能非常强大,非常好用(非免费)

Editor.md

我这里使用的就是Editor.md,作为一个资深码农,Mardown必然是我们程序猿最喜欢的格式,看下面,就爱上了!

解压以后,在examples目录下面,可以看到他的很多案例使用!学习,其实就是看人家怎么写的,然后进行模仿就好了!

我们可以将整个解压的文件倒入我们的项目,将一些无用的测试和案例删掉即可!

数据库设计

建表SQL:

1、建一个SpringBoot项目配置

spring:datasource:username:rootpassword:123456#serverTimezone=UTC解决时区的报错url:jdbc:mysql://localhost:3306/springbootserverTimezone=UTC&useUnicode=true&characterEncoding=utf-8driver-class-name:com.mysql.cj.jdbc.Driversrc/main/java**/*.xmltrue2、实体类:

mybatis: mapper-locations:classpath:com/kuang/mapper/*.xml type-aliases-package:com.kuang.pojo编写一个Controller测试下,是否ok;

1、导入editor.md资源,删除多余文件

@Controller@RequestMapping("/article")publicclassArticleController{@GetMapping("/toEditor")publicStringtoEditor(){return"editor";}@PostMapping("/addArticle")publicStringaddArticle(Articlearticle){articleMapper.addArticle(article);return"editor";}}图片上传问题

1、前端js中添加配置

//图片上传imageUpload:true,imageFormats:["jpg","jpeg","gif","png","bmp","webp"],imageUploadURL:"/article/file/upload",////这个是上传图片时的访问地址2、后端请求,接收保存这个图片,需要导入FastJson的依赖!

@ConfigurationpublicclassMyMvcConfigimplementsWebMvcConfigurer{//文件保存在真实目录/upload/下,//访问的时候使用虚路径/upload,比如文件名为1.png,就直接/upload/1.png就ok了。@OverridepublicvoidaddResourceHandlers(ResourceHandlerRegistryregistry){registry.addResourceHandler("/upload/**").addResourceLocations("file:"+System.getProperty("user.dir")+"/upload/");}}表情包问题

自己手动下载,emoji表情包,放到图片路径下:

修改editormd.js文件

@GetMapping("/{id}")publicStringshow(@PathVariable("id")intid,Modelmodel){Articlearticle=articleMapper.getArticleById(id);model.addAttribute("article",article);return"article";}2、编写页面article.html

分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据。

分布式系统(distributedsystem)是建立在网络之上的软件系统。

首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。。。

Dubbo文档

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,急需一个治理系统确保架构有条不紊的演进。

在Dubbo的官网文档有这样一张图

单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。

缺点:

1、性能扩展比较难

2、协同开发问题

3、不利于升级维护

垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。

缺点:公用模块无法重复利用,开发性的浪费

分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)[ServiceOrientedArchitecture]是关键。

RPC【RemoteProcedureCall】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。

也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调远程函数;

RPC基本原理

步骤解析:

RPC两个核心模块:通讯,序列化。

Dubbo

ApacheDubbo|db|是一款高性能、轻量级的开源JavaRPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

1.了解Dubbo的特性

2.查看官方文档

dubbo基本概念

服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。

服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者

调用关系说明

l服务容器负责启动,加载,运行服务提供者。

l服务提供者在启动时,向注册中心注册自己提供的服务。

l服务消费者在启动时,向注册中心订阅自己所需的服务。

l注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

l服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

点进dubbo官方文档,推荐我们使用Zookeeper注册中心

什么是zookeeper呢?可以查看官方文档

Window下安装zookeeper

1、下载zookeeper:地址,我们下载3.4.14,最新版!解压zookeeper

2、运行/bin/zkServer.cmd,初次运行会报错,没有zoo.cfg配置文件;

可能遇到问题:闪退!

解决方案:编辑zkServer.cmd文件末尾添加pause。这样运行出错就不会退出,会提示错误信息,方便找到原因。

3、修改zoo.cfg配置文件

将conf文件夹下面的zoo_sample.cfg复制一份改名为zoo.cfg即可。

注意几个重要位置:

dataDir=./临时数据存储的目录(可写相对路径)

clientPort=2181zookeeper的端口号

修改完成后再次启动zookeeper

4、使用zkCli.cmd测试

ls/:列出zookeeper根下保存的所有节点

[zk:127.0.0.1:2181(CONNECTED)4]ls/[zookeeper]create–e/kuangshen123:创建一个kuangshen节点,值为123

get/kuangshen:获取/kuangshen节点的值

我们再来查看一下节点

window下安装dubbo-admin

dubbo本身并不是一个服务软件。它其实就是一个jar包,能够帮你的java程序连接到zookeeper,并利用zookeeper消费、提供服务。

但是为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,不过这个监控即使不装也不影响使用。

我们这里来安装一下:

1、下载dubbo-admin

2、解压进入目录

修改dubbo-admin\src\main\resources\application.properties指定zookeeper地址

server.port=7001spring.velocity.cache=falsespring.velocity.charset=UTF-8spring.velocity.layout-url=/templates/default.vmspring.messages.fallback-to-system-locale=falsespring.messages.basename=i18n/messagespring.root.password=rootspring.guest.password=guestdubbo.registry.address=zookeeper://127.0.0.1:21813、在项目目录下打包dubbo-admin

mvncleanpackage-Dmaven.test.skip=true第一次打包的过程有点慢,需要耐心等待!直到成功!

4、执行dubbo-admin\target下的dubbo-admin-0.0.1-SNAPSHOT.jar

java-jardubbo-admin-0.0.1-SNAPSHOT.jar【注意:zookeeper的服务一定要打开!】

安装完成!

框架搭建

1.启动zookeeper!

2.IDEA创建一个空项目;

3.创建一个模块,实现服务提供者:provider-server,选择web依赖即可

4.项目创建完毕,我们写一个服务,比如卖票的服务;

编写接口

packagecom.kuang.provider.service;publicinterfaceTicketService{publicStringgetTicket();}编写实现类

packagecom.kuang.provider.service;publicclassTicketServiceImplimplementsTicketService{@OverridepublicStringgetTicket(){return"《狂神说Java》";}}5.创建一个模块,实现服务消费者:consumer-server,选择web依赖即可

6.项目创建完毕,我们写一个服务,比如用户的服务;

编写service

packagecom.kuang.consumer.service;publicclassUserService{//我们需要去拿去注册中心的服务}需求:现在我们的用户想使用买票的服务,这要怎么弄呢?

服务提供者

1、将服务提供者注册到注册中心,我们需要整合Dubbo和zookeeper,所以需要导包

我们从dubbo官网进入github,看下方的帮助文档,找到dubbo-springboot,找到依赖包

org.apache.dubbodubbo-spring-boot-starter2.7.3zookeeper的包我们去maven仓库下载,zkclient;

#当前应用名字dubbo.application.name=provider-server#注册中心地址dubbo.registry.address=zookeeper://127.0.0.1:2181#扫描指定包下服务dubbo.scan.base-packages=com.kuang.provider.service3、在service的实现类中配置服务注解,发布服务!注意导包问题

importorg.apache.dubbo.config.annotation.Service;importorg.springframework.stereotype.Component;@Service//将服务发布出去@Component//放在容器中publicclassTicketServiceImplimplementsTicketService{@OverridepublicStringgetTicket(){return"《狂神说Java》";}}逻辑理解:应用启动起来,dubbo就会扫描指定的包下带有@component注解的服务,将它发布在指定的注册中心中!

服务消费者

1、导入依赖,和之前的依赖一样;

#当前应用名字dubbo.application.name=consumer-server#注册中心地址dubbo.registry.address=zookeeper://127.0.0.1:21813.本来正常步骤是需要将服务提供者的接口打包,然后用pom文件导入,我们这里使用简单的方式,直接将服务的接口拿过来,路径必须保证正确,即和服务提供者相同;

4.完善消费者的服务类

packagecom.kuang.consumer.service;importcom.kuang.provider.service.TicketService;importorg.apache.dubbo.config.annotation.Reference;importorg.springframework.stereotype.Service;@Service//注入到容器中publicclassUserService{@Reference//远程引用指定的服务,他会按照全类名进行匹配,看谁给注册中心注册了这个全类名TicketServiceticketService;publicvoidbugTicket(){Stringticket=ticketService.getTicket();System.out.println("在注册中心买到"+ticket);}}5.测试类编写

@RunWith(SpringRunner.class)@SpringBootTestpublicclassConsumerServerApplicationTests{@AutowiredUserServiceuserService;@TestpublicvoidcontextLoads(){userService.bugTicket();}}启动测试

1.开启zookeeper

2.打开dubbo-admin实现监控【可以不用做】

3.开启服务者

4.消费者消费测试,结果:

监控中心:

ok,这就是SpingBoot+dubbo+zookeeper实现分布式开发的应用,其实就是一个服务拆分的思想;

安全简介

市面上存在比较有名的:Shiro,SpringSecurity!

这里需要阐述一下的是,每一个框架的出现都是为了解决某一问题而产生了,那么SpringSecurity框架的出现是为了解决什么问题呢?

首先我们看下它的官网介绍:SpringSecurity官网地址

SpringSecurityisapowerfulandhighlycustomizableauthenticationandaccess-controlframework.Itisthede-factostandardforsecuringSpring-basedapplications.

SpringSecurityisaframeworkthatfocusesonprovidingbothauthenticationandauthorizationtoJavaapplications.LikeallSpringprojects,therealpowerofSpringSecurityisfoundinhoweasilyitcanbeextendedtomeetcustomrequirements

SpringSecurity是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。

从官网的介绍中可以知道这是一个权限框架。想我们之前做项目是没有使用框架是怎么控制权限的?对于权限一般会细分为功能权限,访问权限,和菜单权限。代码会写的非常的繁琐,冗余。

怎么解决之前写权限代码繁琐,冗余的问题,一些主流框架就应运而生而SpringScecurity就是其中的一种。

实验环境搭建

1、新建一个初始的springboot项目web模块,thymeleaf模块

2、导入静态资源

welcome.html|views|level11.html2.html3.html|level21.html2.html3.html|level31.html2.html3.htmlLogin.html3、controller跳转!

packagecom.kuang.controller;importorg.springframework.stereotype.Controller;importorg.springframework.web.bind.annotation.PathVariable;importorg.springframework.web.bind.annotation.RequestMapping;@ControllerpublicclassRouterController{@RequestMapping({"/","/index"})publicStringindex(){return"index";}@RequestMapping("/toLogin")publicStringtoLogin(){return"views/login";}@RequestMapping("/level1/{id}")publicStringlevel1(@PathVariable("id")intid){return"views/level1/"+id;}@RequestMapping("/level2/{id}")publicStringlevel2(@PathVariable("id")intid){return"views/level2/"+id;}@RequestMapping("/level3/{id}")publicStringlevel3(@PathVariable("id")intid){return"views/level3/"+id;}}4、测试实验环境是否OK!

SpringSecurity是针对Spring项目的安全框架,也是SpringBoot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入spring-boot-starter-security模块,进行少量的配置,即可实现强大的安全管理!

记住几个类:

“认证”(Authentication)

身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。

身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

这个概念是通用的,而不是只在SpringSecurity中存在。

1、引入SpringSecurity模块

org.springframework.bootspring-boot-starter-security2、编写SpringSecurity配置类

查看我们自己项目中的版本,找到对应的帮助文档:

3、编写基础配置类

我们可以定义认证规则,重写configure(AuthenticationManagerBuilderauth)方法

ThereisnoPasswordEncodermappedfortheid“null”

1、开启自动配置的注销的功能

4、但是,我们想让他注销成功后,依旧可以跳转到首页,该怎么处理呢?

我们需要结合thymeleaf中的一些功能

Maven依赖:

导入命名空间

12、权限控制和注销搞定!

1、开启记住我功能

思考:如何实现的呢?其实非常简单

我们可以查看浏览器的cookie

3、我们点击注销的时候,可以发现,springsecurity帮我们自动删除了这个cookie

THE END
1.定期看狗的七个地方,狗基本不会得病小狗的脚垫也是健康的小窗口哦。健康的狗狗脚垫柔软有弹性,要是发现脚垫发硬干裂,那可能是生病了,特别是体温还高的话,得赶紧送医院。当然,如果只是轻微干燥,那可能是天气或行走多导致的,可以涂点护脚霜。 3、看看那嘴巴 小狗的嘴巴里也有不少健康的小秘密。瞅瞅牙齿,洁白无瑕才是健康的,有牙结石、牙周病等问题...http://www.360doc.com/content/24/1106/12/9889994_1138634342.shtml
2.宠物医院宠物降检查与疫苗接种手册.doc宠物医院宠物健康检查与疫苗接种手册TOC\o"1-2"\h\u12708第1章宠物健康检查概述 424231.1健康检查的重要性 4321361.2健康检查的主要内容与流程 4162第2章宠物疫苗接种常识 591182.1疫苗种类及作用 559502.2疫苗接种时间表 5169812.3疫苗接种注意事项 67196第3章心脏与呼吸系统检查 6304193.1心脏检查 67663.1.1听诊 6195133.1...https://www.renrendoc.com/paper/358268570.html
1.节后复工安全检查情况的报告(精选16篇)我项目部负责施工的柳州华柳佳苑1#--4#楼项目工地,春节后进行了复工前的安全检查,包括施工现场环境、施工机械设备、施工用电以及进场工人安全教育。对检查中发现的问题进行记录,签发整改通知书,限期整改。现将整改情况汇报如下: 1、施工电梯16层以上的防护门春节前未安装完,现已经安排安装人 ...https://www.360wenmi.com/f/filejm0ikbbl.html
2.web.quanmingzh.cn/nodenews/12095396.htm狗狗射速好烫太怕了视频 熟老太太BB乱螥 女刑警被?虐乳高潮在线观看 玛雅主管德711.112扣 91i美国黄色观播放 兔费许夜爽爽爽视频肉榛 日本动漫人物拔萝卜打扑克 美女鲍黄片 色色热综合 日韩精品视频一区二区 欧美A级肉欲大片 欧美经典阁在线午夜 含羞草电影免费看韩国 免费看a特级黄片 果冻传媒2021精...http://web.quanmingzh.cn/nodenews/12095396.htm
3.nb.qglk2.com/mmmj497556252024-11-10 16:33:58 WRITEAS检查身体 2024-11-10 16:33:58 日韩天码砖区2021 2024-11-10 16:33:58 如何将自己隐私玩到哭 2024-11-10 16:33:58 宝贝再快一点别停 2024-11-10 16:33:58 韵母攻略掌中美母 2024-11-10 16:33:58 夹枪带棒1V2舒明明黎远荣 2024-11-10 16...http://nb.qglk2.com/mmmj49755625
4.quanshengyellow字幕在线观看免费观看直播 狗狗东西在我的下面越来越大 h少女漫画没有马赛克 教授公大JI巴好好爽好深H 光棍儿电影下载 欧洲vodafonewifi18大豆行情 白姐免费阅读全文 女子三级片网站 囯产精品美女 按在怀里用巴掌打到哭的视频 女人扒开自己的荫道口 一个人看的www视频韩国 欧亿8娱乐 麻7IIII2扣不...http://quansheng-bj.com/apfnode70171524.html
5.www.senthb.cn/mmmj/828169.htm人妻石原莉奈一8MAV 狗狗插我好深好硬 无限动漫视频在线观看 50岁老熟女毛多水毛 任你躁这里有精品2 视频 神艺缘套图2010,10,19,第100期模特馨缘50? 红桃漫画羞羞网站 小受被路人灌满NP PKONXXXX 黄色网站在线免费观看 99精品麻豆国产综合无码老男人 人妻互换HDF中文 老湿机美女尤物 白洁被多p...http://www.senthb.cn/mmmj/828169.htm
6.whld.longdalvye.com/mmmj96847140/904321.shtml狗狗射速好烫的故事 耀世注册企q586.601鹅 大学生粉嫩喷白浆 2023AV天堂网在线观看 久久久精品综合区亚洲 色欲av又大又粗又硬 检查 玩弄 强迫 禁锢 h 女生越叫男生就越有快 十大禁用黄台免费看 欧美天天爽天天夜夜爽毛 国产高潮流白浆视频 欧洲gramy80老妇人 欧美色色视频 GOGOGO大胆日本群交 波斯妓女Bw...http://whld.longdalvye.com/mmmj96847140/904321.shtml
7.web.zjnsbank.cn/nodenews/71002438.htm夹一天不能掉晚上我检查若若 人妻不人y戋y夕中出 成人看三级片免费播放 免费三级毛片 XXXXHD裸体瑜伽video 丁香九月婷 女人和公豬交交30 亚洲一级淫片 国产一级aa无码大片免费 春明润被部长侵犯人妻系列 欧美特黄BBBBBBBBB 泰国裸体肏屄BWWBWW 久久无码精品福利大桥未久 天亿平台 雀7IIII2扣安稳 有后宫...http://web.zjnsbank.cn/nodenews/71002438.htm
8.epaper.hardwarecity.com.cn/apfnews68310988.htm狗狗的东西比老公的大 无码A级毛片免费网站 一性一爱一乱一怆一情 路线一路线二线路三满18 甘雨とぱんぱん(原神) 从背后抓住胸前的两只大白兔 slippery动画在线观看免费 岳两片肥美的蚌肉 二人生猴子不盖被子 色管在线 色欲无码婬片吃奶高潮 午夜寂寞支持安卓精品 老司机电影天堂 天美果冻视频大全英文版...http://epaper.hardwarecity.com.cn/apfnews68310988.htm
9.www.shuofangjituan.com/apfnews50040311.shtml狗狗趴在我身上打扑克 英语课代表在教室帮我弄 24.15MB 28%好评590人) 免费国产成人高清在线网站东京 女人阴性毛 图片 美女AA性爱毛片免费看 45.35MB 12%好评8523人) 3.0.3免费oppo版大全在线观看 顾心怡大尺度无圣光在线观看 美女鸡巴铁打 67.76MB 16%好评53人) 如何在被子里无声自W 继续喷...http://www.shuofangjituan.com/apfnews50040311.shtml
10.web.hzaqdq.com/nodenews/313708.htm乖乖戴着玉势等我检查 8天前 清纯校花第一夜献身老板 教练把车开到没人的地方 8天前 美女露出强行被男生揉小说 公与媳乱LUAN日小丹 3天前 麻豆果冻传媒新剧国产短视频 国产ZLJZLJZLJZLJ18 6天前 老师你乖乖的可以少吃点苦头 深喉电影完整版百度影音 8天前 啊哦快到了再用力一点 流水了不要摸了上课...http://web.hzaqdq.com/nodenews/313708.htm
11.njbywd.com/mmmj/624099.htm说到这里,有关iPhone16的情报就结束了,这也给下周的发布会留下悬念——市场心心念念的"苹果智能"到底怎么样了。虽然这批新手机上市时,大多数用户根本就用不到所谓的苹果智能,而且多项核心功能至少要到明年初才能推出,但下周好歹也是苹果管理层"公开画饼"的时机。 http://njbywd.com/mmmj/624099.htm
12.1212.yonana.cn/mmmj31457298/283441.html欧美大鸡吧干大逼洞小黄片兔费上线 狗狗的速度好快像打桩机 日韩 有码 一区 二区 宁荣荣被唐三桶叫个不停小说 偷拍美女白带视频 老外看暴躁老外视频 国产美女69视频免费观看 jul-366在线 黄业网站在线观看 亚洲男人天堂 日本老熟妇与小伙乱伦 欧美xi人大胆子女艺术 中国老太大毛茸茸视频 张柏芝黄瓜门事件...http://1212.yonana.cn/mmmj31457298/283441.html
13.www.sxuetec.com/mmmj75420225.shtml精 液检查3d动漫 0天前 写的超细的开车秒湿 啊cao死你个浪货尿 0天前 多人野外强伦姧人妻完整版 农田丰满艳肉妇HD 9天前 父子纯h文 亏亏的视频带疼痛声的免安装 8天前 武侠 欧美 另类 人妻 他从客厅把我干到卧室体验 5天前 国产毛2卡3卡4卡视频免费 上课没带罩子让他c了一节课 5天前 亚...http://www.sxuetec.com/mmmj75420225.shtml
14.web.zhengshunboli.com/nodenews/922715.htm在两位80后创始人的带领下,海辰储能成长迅速。据高工产业研究院(GGII)统计,2022年海辰储能出货量达5GWh,位列储能锂电池企业出货量第七。海辰储能还在去年实现了“国内电力储能电池交付项目数量”和“国内储能电池出货量增速”两项排名的第一。-。 在该殡仪馆内,一位遇难者家属告诉新黄河记者,他的女儿是当地二十八中...http://web.zhengshunboli.com/nodenews/922715.htm
15.收藏90+深度学习开源数据集整理:包括目标检测工业缺陷图像分割...这可能有助于学习上下文信息,(e) 巨大的对象尺度变化,通常在同一图像内包含小、中和大对象,(f) 图像内具有不同方向的对象的不平衡和不均匀分布,描绘真实-生活空中条件,(g)几个小尺寸物体,外观模糊,只能通过上下文推理来解决,(h)由专业注释者执行的精确实例级注释,由符合良好规范的专家注释者交叉检查和验证定义的...https://blog.csdn.net/Yong_Qi2015/article/details/124535517
16.煤矿调度记录台账8篇(全文)6、继续加强对地面巡查人员及矿井带班人员的调度考核,确保检查质量。负责人贾林、亓瑞强 完成时间:9月30日 考核人:金矿长 以上各项工作必须按期保量的完成,否则,每项对负责人罚款100元。 煤矿调度记录台账 第2篇 山西乡宁焦煤集团申南凹焦煤有限公司2014年记录收发台账 序号 记录名称 月份 上交人员 上交时间 接受...https://www.99xueshu.com/w/filevx5dgceg.html
17.web.lygtjzx.com/nodenews/667004.shtml11月21日,在市教委第16次疫情防控工作视频调度会上,重申了流感高发时学校线上线下教学切换的标准,强调病情比较严重的学校或班级要按照标准和程序进行线上线下教学的切换,该启动线上教学就要果断启动。-。 中企海外子公司黄金被劫... 金砖国家领导人第十五次会晤定于8月22日至24日在南非约翰内斯堡举办。法国《言论...http://web.lygtjzx.com/nodenews/667004.shtml
18.www.ederbou.com/mmmj/510548.shtml小柔涂了春药被一群人伦动态图 午夜医生阴道检查视频 黄改动漫在线观看 被六个男人躁到一夜视频 在镜子面前玩你H 欧美成人另类 云缨被捅到流口水一脸舒服的表情 精品乱伦子 白菜项目网 videos操逼的视频 欧美老妇女αⅴ视频 男人插曲女人下生视频在线看 白洁性荡生活 五月婷婷六月激情 老师今晚让你弄个...http://www.ederbou.com/mmmj/510548.shtml
19.幼犬到家后的注意事项有些主人对狗狗过度溺爱,允许狗狗在床上睡觉,认为“这就是爱”。但其实在狗狗的意识里,只有等级意识,也就是说,它才是老大,你是它的小弟,是它领导你,而不是你领导它。养成这种意识的狗狗,未来会有很多的行为学问题,比如护食、无端吠叫、不允许陌生人接近家里人、出门爱打架等;特别是当你想改正它的某项错误...https://mip.oh100.com/a/201704/536644.html
20.记录给16岁狗狗做子宫摘除手术!已做手术!出院啦4.27我去过北辰的乖乖,但是因为16岁发烧被要求直接全身检查……1.没有使用听诊器听诊 2.没有说做什么项目,如果和我们说先测个血、先做个b超,我们一定不会有任何异议! 4.28换了一家恩雅宠物医院,结果医生在抽血时未经允许擅自给狗狗埋留置针 (我们发现之后问为什么没提前告诉我们,医生说没要你们55元钱,你们应该...https://www.douban.com/group/topic/223579467/
21.直通部委今年春运防疫坚决防止“层层加码”南水北调工程调水...会议指出,2021年1月至11月,全国共审计6.1万多个单位,促进增收节支和挽回损失3040多亿元,健全完善规章制度9900多项。主要工作包括:保障中央政令畅通和项目落实落地,深入开展重大政策和重大投资审计;维护国家经济安全,深入揭示重大经济风险隐患;推动保障和改善民生,坚持审计力量下沉基层;维护财经纪律严肃性,着力检查中央八项...https://www.jiemian.com/article/6985755.html