工控智汇

工控智汇

四篇文章玩转SpringBoot——4starter组件

admin 41 35
上次回顾

理解springboot的总体启动流程,并能口述大概

理清配置文件的加载流程

学习目标

明确starter组件到底是什么?是做什么的?

自己实现一个starter组件

SpringBoot提供的Starter组件和第三方Starter组件

Spring-Boot-Web-Starter如何自动让应用部署到Tomcat容器的

第1章starter组件简介

starter组件是SpringBoot的一个核心特性,Starter组件的出现极大简化了项目开发,例如在项目中使用的文件下配置:

/groupIdartifactIdspring-boot-starter-web/artifactId/depency

SpringBoot就会自动关联web开发相关的依赖,如tomcat以及spring-webmvc等,进而对web开发进行支持,同时相关技术也将实现自动配置,避免了繁琐的配置文件。

利用starter实现自动化配置需要两个条件:Maven依赖、配置文件,Maven依赖实质上就是导入jar包,SpringBoot启动的时候会找到Starter组件jar包中的resources/META-INF/文件,根据文件中的配置,找到需要自动配置的类。

starter组件理解总结:

每个不同的starter组件实际上完成自身的功能逻辑,然后对外提供一个bean对象让别人调用

对外提供的bean通过自动装配原理注入到提供方的IoC容器中

第2章手写starter组件

要实现一个自己的starter组件其实也很简单,要完成一个starter组件的编写,首先要明确,我们要做的事有哪些:

通过配置类提供对外服务的bean对象

按照自动装配原理完成的编写

starter自动属性配置

接下来我们就来手写一个starter组件,流程如下:

1.创建一个springboot项目:redisson-spring-boot-starter

2.引入依赖

?xmlversion="1.0"encoding="UTF-8"?projectxmlns=""xmlns:xsi=""xsi:schemaLocation=""//groupIdartifactIdredisson-spring-boot-starter//versionnameredisson-spring-boot-starter/name!--FIXMEchangeittotheproject'swebsite--url;/////propertiesdepenciesdepencygroupIdjunit/groupIdartifactIdjunit//versionscopetest/scope//groupIdartifactIdspring-boot-starter//versionoptionaltrue/optional//groupIdartifactIdredisson//version//groupIdartifactIdspring-boot-configuration-processor//version/depency/depenciesbuildpluginManagement!--lockdownpluginsversionstoavoidusingMavefaults(maybemovedtoparentpom)--plugins!--cleanlifecycle,see;pluginartifactIdmaven-clean-plugin//version/plugin!--defaultlifecycle,jarpackaging:see;pluginartifactIdmaven-resources-plugin//version/pluginpluginartifactIdmaven-compiler-plugin//version/pluginpluginartifactIdmaven-surefire-plugin//version/pluginpluginartifactIdmaven-jar-plugin//version/pluginpluginartifactIdmaven-install-plugin//version/pluginpluginartifactIdmaven-deploy-plugin//version/plugin!--sitelifecycle,see;pluginartifactIdmaven-site-plugin//version/pluginpluginartifactIdmaven-project-info-reports-plugin//version/plugin/plugins/pluginManagement/build/project

3.创建要注入的bean类和接口

;/***@authorEclipse_2019*@create2022/6/119:58*/publicinterfaceHumen{Stringdancing();};;;;;;/***@authorEclipse_2019*@create2022/1/1422:55*/publicclassGirlimplementsHumen{publicstaticGirlcreate(Stringname){returnnewGirl(name);}privateStringname;publicGirl(Stringname){=name;}publicStringgetName(){returnname;}publicvoidsetName(Stringname){=name;}@OverridepublicStringdancing(){returnname+"喜欢跳舞";}}

4.创建属性类

;;/***@authorEclipse_2019*@create2021/8/1111:28*/@ConfigurationProperties(prefix="")publicclassGirlProperties{privateStringname="wentai";publicStringgetName(){returnname;}publicvoidsetName(Stringname){=name;}}

5.创建配置类

;;;;;;;;;/***@authorEclipse_2019*@create2021/8/1111:05*/@ConditionalOnClass()//条件装配@EnableConfigurationProperties()@ConfigurationpublicclassGirlAutoConfiguration{@BeanHumenhumen(GirlPropertiesgirlProperties){(());}}

6.实现自动装配流程,在META-INF目录下创建文件

=\,\

7.在META-INF创建属性默认规范文件

{"properties":[{"name":"","type":"","description":"Redis的服务器地址","defaultValue":"localhost"},{"name":"","type":"","description":"Redis的服务器端口","defaultValue":6379},{"name":"","type":"","description":"Redis的服务器密码","defaultValue":"jingtian"},{"name":"","type":"","description":"Redis的服务器库","defaultValue":0},{"name":"","type":"","description":"默认女孩名","defaultValue":"wentai"}]}

8.打包发布

9.测试

通过上面我们实现自己的starter组件案例来看,starter组件的实现其实逻辑并不复杂,核心思想就是在META-INF目录下创建文件,然后配置自定义的配置类。只要按照这个逻辑配置,都可以做到自动注入到IoC容器中去,OK,那我们现在来看看我们的spring-boot-starter-data-redis这个starter组件,你会发现,这个组件里面居然没有文件,为什么呢?没有这个文件,它是怎么自动装配的呢?

第3章自身与第三方维护

其实针对springboot的starter组件分为两类

1.springboot自身维护的starter组件

所有的starter组件自身不带文件,集中在spring都是-boot-autoconfigure包下的EnableAutoConfiguration

springboot装配这些配置类是需要条件的,不可能所有的configuration都注入,假设我没用到redis的话就不会引包,这样就根据@ConditionalOnClass()在classpath下找不到RedisOperation类,这样就不会加载该配置类

自身维护的starter组件的命名:spring-boot-starter-XXX

2.第三方维护的starter组件

自己维护文件

命名方式:XXX-spring-boot-starter

3.这里有个小知识:@ConditionalOnClass()在我们本地用的时候,如果不存在的话压根编译不能通过,但是为什么springboot自身维护的能编译通过呢?

其实原因也简单,因为在starter组件编译的时候是引入了@ConditionnalOnClass里面的那个类了的,然后在pom文件引入的这个XXX类所在的jar包时加了true,等starter组件编译打包之后不会将XXX类所在的jar包传递依赖给别的项目。

这里就可以将spring-boot-autoconfigure包理解成一个starter组件,它在编译的过程中引入了很多jar包,比如说引入Redis的相关jar包,然后加入了true,当autoconfigure编译打成jar包之后是没问题的,但是别的项目依赖autoconfigure之后,必须要引入redis的jar包才能通过@ConditionalOnClass注解。

现在我们会手写自己的starter组件了,也明白了不同组件的区别,那么接下来让我们一起来看看springboot中的一个比较重要的组件——spring-boot-starter-web组件,为什么要看它呢?因为它帮我们完成了容器的内置以及启动。

第4章内置容器4.1starter-web

1.Springboot整合SpringMVC只需要在文件中引入

/groupIdartifactIdspring-boot-starter-web/artifactId/depency

2.配置文件

server:port:8080启用文件上传location:单个文件上传最大大小限制max-request-size:100MB日期格式date-time:yyyy-MM-ddHH:mm:ss时间格式servlet:path:/匹配静态资源路径view:prefix:view后缀,如:.jsp

以上是SpringMVC常用配置,更多配置可参见

我们只配置最简单的

=.=/WEB-INF/jsp/

3.为项目添加WEB-INF目录和文件


4.service

;;/***@authorEclipse_2019*@create2022/6/1116:03*/@ServicepublicclassJspService{publicStringsayHello(Stringname){return"你真棒!"+name;}}

5.controller

;;;;;;/***@authorEclipse_2019*@create2022/6/1116:03*/@RestControllerpublicclassJspController{@AutowiredprivateJspServicejspService;@RequestMapping("/jsp")publicModelAndViewhello(@RequestParamStringname){ModelAndViewmodelAndView=newModelAndView();("version","2.X版本");("name",name);("msg",(name));("a");returnmodelAndView;}}

6.jsp

%@pagecontentType="text/html;charset=UTF-8"language="java"%!DOCTYPEhtmlhtmllang="en"bodyh2${version}/h2h2${name}:${msg}/h2/body/html

上面的案例实现了Springboot集成springmvc,但是现在还没有哪里用到了容器,那容器是怎么启动的呢?

先来看看spring-boot-starter-web包里面有啥

ServletWebServerFactoryAutoConfiguration配置类中Import了

当容器启动的时候也会自动装配该类,在该配置类中创建了TomcatServletWebServerFactory()

WebMvcAutoConfiguration类完成了InternalResourceViewResolver解析器的注入

然后再来看看springboot启动的时候是怎么去创建内置容器的

4.2onRefresh

spring容器启动代码就不说了,这里主要看一下onRefresh()这个方法。转到定义发现这个方法里面啥都没有,这明显是一个钩子方法,它会钩到它子类重写onRefresh()方法。所以去看子类里面的onRefresh()

protectedvoidonRefresh()throwsBeansException{//这是一个空方法,AbstractApplicationContext这个类是一个抽象类,//所以我们要找到集成AbstractApplicationContext的子类,去看子类里面的onRefresh()//Forsubclasses:donothingbydefault.}


我们这里是一个Web项目,所以我们就去看ServletWebServerApplicationContext这个类,我还是把类的关系图贴一下


我们就去看ServletWebServerApplicationContext这个类下面的onRefresh()方法

protectedvoidonRefresh(){();try{//看到内置容器的影子了,进去看看createWebServer();}catch(Throwableex){thrownewApplicationContextException("Unabletostartwebserver",ex);}}
4.3createWebServer
privatevoidcreateWebServer(){WebServerwebServer=;ServletContextservletContext=getServletContext();if(webServer==nullservletContext==null){//1、这个获取webServerFactory还是要进去看看ServletWebServerFactoryfactory=getWebServerFactory();=(getSelfInitializer());}elseif(servletContext!=null){try{getSelfInitializer().onStartup(servletContext);}catch(ServletExceptionex){thrownewApplicationContextException("Cannotinitializeservletcontext",ex);}}initPropertySources();}

我们继续看下getWebServletFactory()这个方法,这个里面其实就是选择出哪种类型的web容器了

protectedServletWebServerFactorygetWebServerFactory(){//Usebeannamessothatwedon'tconsiderthehierarchyString[]beanNames=getBeanFactory().getBeanNamesForType();if(==0){thrownewApplicationContextException("UnabletostartServletWebServerApplicationContextduetomissing"+"ServletWebServerFactorybean.");}if(){thrownewApplicationContextException("UnabletostartServletWebServerApplicationContextduetomultiple"+"ServletWebServerFactorybeans:"+(beanNames));}returngetBeanFactory().getBean(beanNames[0],);}
4.4getWebServer

我们再回头去看(getSelfInitializer()),转到定义就会看到很熟悉的名字tomcat

publicWebServergetWebServer(ServletContextInitializerinitializers){//tomcat这位大哥出现了Tomcattomcat=newTomcat();FilebaseDir=(!=null?:createTempDir("tomcat"));(());Connectorconnector=newConnector();().addConnector(connector);customizeConnector(connector);(connector);().setAutoDeploy(false);configureEngine(());for(ConnectoradditionalConnector:){().addConnector(additionalConnector);}prepareContext((),initializers);returngetTomcatWebServer(tomcat);}

内置的Servlet容器就是在onRefresh()方法里面启动的,至此一个Servlet容器就启动OK了。