<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何從零開始寫一個xxx-spring-boot-stop

          共 18100字,需瀏覽 37分鐘

           ·

          2023-10-14 13:32

          不好意思,是xxx-spring-boot-starter。
          Spring Boot與Spring最大的區(qū)別就是自動配置了,那你知道Spring Boot是如何完成自動配置的嗎?
          我們又如何編寫自己的xxx-spring-boot-starter,進(jìn)而完成自定義的自動配置。
          又要如何測試自定義的starter在不同集成環(huán)境下的表現(xiàn)呢?
          今天的文章給大家一一道來。

          1

          從auto configuration說起

          Spring Boot和Spring最大的區(qū)別在哪,答案是自動配置。
          Spring的概念很大,這里的Spring只是指 Spring Framework。
          實際現(xiàn)在的開發(fā)應(yīng)該沒有直接用Spring的了,大都是通過Spring Boot來間接使用Spring。
          包括官方也是這么建議的。
          If you are just getting started with Spring, you may want to begin using the Spring Framework by creating a Spring Boot-based application. Spring Boot provides a quick (and opinionated) way to create a production-ready Spring-based application. 
          上面這段話的意思是建議你直接通過Spring Boot來開始。
          我們下面將一步步地來講解Spring Boot是如何做到自動配置的。
          熟悉Spring的小伙伴應(yīng)該都熟悉一個概念,叫做Beans。Beans是什么呢,在Spring中,你代碼中的所有被Spring IoC容器所管理的實例,就叫做Beans。
          Spring IoC容器,是Spring框架最核心的概念。
          Spring IoC Container
          Spring最初Bean是通過xml配置的,如下所示:
          <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://www.springframework.org/schema/beans        https://www.springframework.org/schema/beans/spring-beans.xsd">
          <bean id="transferService" class="com.acme.TransferServiceImpl"/>
          <!-- more bean definitions go here -->
          </beans>
          大家是不是好久沒見過xml配置的方式了,說實話我也好久沒見到了,哈哈。
          當(dāng)然Spring也支持通過代碼注解的方式來配置Bean。
          @Configurationpublic class AppConfig {
          @Bean public TransferServiceImpl transferService() { return new TransferServiceImpl(); }}

          上面通過注解和xml配置的方式是等效的。
          實際無論是何種配置方式,通過xml也好,通過@Bean代碼配置也罷,還是通過自動配置(實際也是通過@Bean),最終都是將類的實例注入到Spring的容器中去,及IOC Container。
          Spring Boot的自動配置,實際是通過spring-boot-autoconfigure模塊配合其他spring-boot-starter-xxx模塊來完成的。
          spring-boot-autoconfigure
          spring-boot-autoconfigure模塊的spring.factories類中定義了眾多的XXXAutoConfiguration類,在符合一定的條件的時候(比如判斷Classpath中是否有相應(yīng)的類,也就是項目中是否引入了相關(guān)的依賴,實際就是是否引入了相應(yīng)的spring-boot-starter-xxx模塊),則會自動進(jìn)行相應(yīng)的配置,這里的自動配置實際就是配置Spring容器中的Bean。
          下面具體舉例說明。

          2

          常見的spring-boot-starter-xxx

          我們以spring-boot-starter-data-redis為例,看下該starter是如何與spring-boot-autoconfigure模塊來配合,進(jìn)而自動注入Redis相關(guān)Bean的。
          我們打開spring-boot-starter-data-redis jar包,會發(fā)現(xiàn)jar包里什么代碼都沒有?
          spring-boot-starter-data-redis jar
          沒錯,是這樣的,spring-boot-starter-data-redis模塊無需有任何代碼,只需要有依賴就可以了。這與官方的文檔也是一致的。

          1. The autoconfigure module that contains the auto-configuration code for "acme". 2. The starter module that provides a dependency to the autoconfigure module as well as "acme" and any additional dependencies that are typically useful.

          上面文檔說明,完整的starter一般是由兩部分組成,一個是autoconfigure模塊,另一個是starter模塊,autoconfigure模塊包含自動配置的代碼,starter模塊只包括依賴引用。
          對應(yīng)這里的示例,autoconfigure模塊對應(yīng)的是spring-boot-autoconfigure,starter模塊對應(yīng)的是spring-boot-starter-data-redis。
          那就讓我們看看spring-boot-starter-data-redis包括了哪些引用,spring-boot-starter-data-redis的pom文件依賴如下:
          <?xml version="1.0" encoding="UTF-8"?><project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">  <!-- This module was also published with a richer model, Gradle metadata,  -->  <!-- which should be used instead. Do not delete the following line which  -->  <!-- is to indicate to Gradle or any Gradle module metadata file consumer  -->  <!-- that they should prefer consuming it instead. -->  <!-- do_not_remove: published-with-gradle-metadata -->  <modelVersion>4.0.0</modelVersion>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-data-redis</artifactId>  <version>2.4.2</version>  <name>spring-boot-starter-data-redis</name>  <description>Starter for using Redis key-value data store with Spring Data Redis and the Lettuce client</description>  <url>https://spring.io/projects/spring-boot</url>  <organization>    <name>Pivotal Software, Inc.</name>    <url>https://spring.io</url>  </organization>  <licenses>    <license>      <name>Apache License, Version 2.0</name>      <url>https://www.apache.org/licenses/LICENSE-2.0</url>    </license>  </licenses>  <developers>    <developer>      <name>Pivotal</name>      <email>[email protected]</email>      <organization>Pivotal Software, Inc.</organization>      <organizationUrl>https://www.spring.io</organizationUrl>    </developer>  </developers>  <scm>    <connection>scm:git:git://github.com/spring-projects/spring-boot.git</connection>    <developerConnection>scm:git:ssh://[email protected]/spring-projects/spring-boot.git</developerConnection>    <url>https://github.com/spring-projects/spring-boot</url>  </scm>  <issueManagement>    <system>GitHub</system>    <url>https://github.com/spring-projects/spring-boot/issues</url>  </issueManagement>  <dependencies>    <dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter</artifactId>      <version>2.4.2</version>      <scope>compile</scope>    </dependency>    <dependency>      <groupId>org.springframework.data</groupId>      <artifactId>spring-data-redis</artifactId>      <version>2.4.3</version>      <scope>compile</scope>    </dependency>    <dependency>      <groupId>io.lettuce</groupId>      <artifactId>lettuce-core</artifactId>      <version>6.0.2.RELEASE</version>      <scope>compile</scope>    </dependency>  </dependencies></project>
          可以看到spring-boot-starter-data-redis模塊主要是引入了Spring Redis及Lettuce相關(guān)的依賴,那么這些依賴到底是如何與spring-boot-autoconfigure配合從而完成自動注入的?
          spring-boot-autoconfigure redis autoconfigure
          spring-boot-autoconfigure模塊中的spring.factories文件中,有關(guān)Redis的自動配置類如上,我們這里主要看RedisAutoConfiguration這個類。
          @Configuration(proxyBeanMethods = false)// 這里表示只有項目依賴中有RedisOperations這個類,下面的配置才會生效,正是因為我們引入了spring-boot-starter-data-redis依賴,然后項目中才會有RedisOperations類,所以該自動配置才會生效@ConditionalOnClass(RedisOperations.class)// RedisProperties為redis相關(guān)的配置,包括集群地址、連接池配置等信息都可以通過這個類來進(jìn)行配置@EnableConfigurationProperties(RedisProperties.class)// 這里是導(dǎo)入了其他的配置類,為Redis連接池相關(guān)的配置@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })public class RedisAutoConfiguration {
          @Bean @ConditionalOnMissingBean(name = "redisTemplate") @ConditionalOnSingleCandidate(RedisConnectionFactory.class) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; }
          @Bean @ConditionalOnMissingBean @ConditionalOnSingleCandidate(RedisConnectionFactory.class) public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }
          }
          就是這個簡單的配置類,將會自動幫我們完成Redis的Lettuce連接池Bean、Spring的RedisTemplate Bean和StringRedisTemplate Bean的注入,無需我們寫一行代碼。
          反應(yīng)在實際的項目編碼中,我們在引入了spring-boot-starter-data-redis依賴后,無需編寫一行代碼,就可以直接使用RedisTemplate來操作Redis了,示例如下:
          @Servicepublic class CacheService {        // 因為Spring Boot已經(jīng)自動注入了StringRedisTemplate,所以這里我們代碼里直接使用即可    @Autowired    private StringRedisTemplate redisTemplate;
          public void set(String key, Object value) { redisTemplate.opsForValue().set(key, JSON.toJSONString(value)); }
          public void set(String key, Object value, long expireTimeout, TimeUnit timeUnit) { redisTemplate.opsForValue().set(key, JSON.toJSONString(value), expireTimeout, timeUnit); }
          public <T> T get(String key, Class<T> clazz) { return JSON.parseObject(redisTemplate.opsForValue().get(key), clazz); }
          public void delete(String key) { redisTemplate.delete(key); }}
          可以看到,Spring通過spring-boot-autoconfigure模塊配合其他spring-boot-starter-xxx模塊從而達(dá)成的自動配置,極大的方便了項目的開發(fā)。
          當(dāng)然這些技術(shù)細(xì)節(jié)隱藏在背后,看似方便實際對開發(fā)的要求反而更高,畢竟在我看來,優(yōu)秀的程序員不應(yīng)該只知道悶頭用,還要知道背后的原理。

          3

          寫一個自己的

          xxx-spring-boot-starter

          既然Spring Boot的starter這么好用,那么我們?nèi)绾螌憘€自己的starter呢?
          當(dāng)然我們在寫自己的starter之前,應(yīng)該了解starter的目標(biāo)及使用場景,在我看來starter的主要使用場景就是方便項目開發(fā)及集成,減少冗余代碼。
          官方建議starter需要包含configure模塊及starter模塊,一個模塊負(fù)責(zé)自動配置的代碼,另一個模塊只負(fù)責(zé)依賴的引入,但如果我們的starter需求簡單的話,也無需分成兩個模塊,寫在一個模塊也就可以了。
          3.1 關(guān)于starter的命名
          Spring官方的starter命名規(guī)范為spring-boot-starter-xxx,非官方的starter命名規(guī)范為xxx-spring-boot-starter。
          像jasypt-spring-boot-starter、knife4j-spring-boot-starter等都是常見的第三方starter。
          所以我們?nèi)绻约簩憇tarter一定要遵循相應(yīng)的規(guī)范。
          在這節(jié)的示例中,我將會寫一個定時任務(wù)的starter,命名為scheduler-spring-boot-starter,該starter在其他模塊引入后將會自動開啟運行特定的定時任務(wù),同時支持任務(wù)名稱和運行間隔時間的自定義配置。
          3.2 scheduler-spring-boot-starter關(guān)鍵代碼
          直接看代碼,關(guān)鍵代碼如下,完整項目代碼獲取方式在文末。
          SchedulerProperties配置類:
          @ConfigurationProperties(prefix = "scheduler")public class SchedulerProperties {
          /** * 定時任務(wù)調(diào)度時間,單位ms,默認(rèn)值1000ms */ private long period = 1000L;
          /** * 定時任務(wù)名稱 */ private String taskName;
          public long getPeriod() { return period; }
          public void setPeriod(long period) { this.period = period; }
          public String getTaskName() { return taskName; }
          public void setTaskName(String taskName) { this.taskName = taskName; }}
          SchedulerAutoConfiguration自動配置類:
          @Configuration@EnableConfigurationProperties(SchedulerProperties.class)@Import(SchedulerExecutorConfiguration.class)public class SchedulerAutoConfiguration {
          @Bean public Scheduler schedulerTask(ScheduledThreadPoolExecutor scheduledThreadPoolExecutor, SchedulerProperties schedulerProperties) { return new Scheduler(scheduledThreadPoolExecutor, schedulerProperties); }}
          SchedulerExecutorConfiguration配置類:
          @Configurationpublic class SchedulerExecutorConfiguration {
          @Bean public ScheduledThreadPoolExecutor scheduledThreadPoolExecutor() { return new ScheduledThreadPoolExecutor(1); }}
          Scheduler類:
          public class Scheduler {
          public static final Logger LOGGER = LoggerFactory.getLogger(Scheduler.class);
          private final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor; private final SchedulerProperties schedulerProperties;
          public Scheduler(ScheduledThreadPoolExecutor scheduledThreadPoolExecutor, SchedulerProperties schedulerProperties) { this.scheduledThreadPoolExecutor = scheduledThreadPoolExecutor; this.schedulerProperties = schedulerProperties; this.init(); }
          public void init() { scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> { LOGGER.info("scheduler task [{}], period [{}ms], currentTime [{}]", schedulerProperties.getTaskName(), schedulerProperties.getPeriod(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); }, 0, schedulerProperties.getPeriod(), TimeUnit.MILLISECONDS); }}
          spring.factories配置文件:
          org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.example.scheduler.SchedulerAutoConfiguration
          scheduler-spring-boot-starter模塊編寫完成并且打包上傳到倉庫后,其他項目只需引入相應(yīng)的依賴,就能實現(xiàn)自動配置運行定時任務(wù)處理的功能。
          依賴示例如下:
          <dependency>   <groupId>com.example</groupId>   <artifactId>scheduler-spring-boot-starter</artifactId>   <version>${scheduler-spring-boot-starter-version}</version></dependency>
          依賴后,項目無需任何處理,將自動運行定時任務(wù):
          2023-10-11 14:22:20.683  INFO 25680 --- [pool-1-thread-1] com.example.scheduler.Scheduler          : scheduler task [test-example], period [5000ms], currentTime [2023-10-11 14:22:20]2023-10-11 14:22:25.689  INFO 25680 --- [pool-1-thread-1] com.example.scheduler.Scheduler          : scheduler task [test-example], period [5000ms], currentTime [2023-10-11 14:22:25]2023-10-11 14:22:30.685  INFO 25680 --- [pool-1-thread-1] com.example.scheduler.Scheduler          : scheduler task [test-example], period [5000ms], currentTime [2023-10-11 14:22:30]2023-10-11 14:22:35.681  INFO 25680 --- [pool-1-thread-1] com.example.scheduler.Scheduler          : scheduler task [test-example], period [5000ms], currentTime [2023-10-11 14:22:35]
          并且還可以通過配置文件來靈活配置任務(wù)名和定時任務(wù)調(diào)度時間:
          配置
          可以看到配置項與IDEA集成的非常好,能夠自動進(jìn)行提示,這里是因為我們在starter模塊中引入了spring-boot-configuration-processor,能夠幫我們生成spring-configuration-metadata.json文件:
          {  "groups": [    {      "name": "scheduler",      "type": "com.example.scheduler.SchedulerProperties",      "sourceType": "com.example.scheduler.SchedulerProperties"    }  ],  "properties": [    {      "name": "scheduler.period",      "type": "java.lang.Long",      "description": "定時任務(wù)調(diào)度時間,單位ms",      "sourceType": "com.example.scheduler.SchedulerProperties"    },    {      "name": "scheduler.task-name",      "type": "java.lang.String",      "description": "定時任務(wù)名稱",      "sourceType": "com.example.scheduler.SchedulerProperties"    }  ],  "hints": []}
          到這里,我們就完成了自定義的xxx-spring-boot-starter開發(fā),示例雖然比較簡單,但麻雀雖小,五臟俱全,這里給大家以參考。

          4

          測試你的spring-boot-starter


          除了真正的將starter引入到項目中進(jìn)行集成測試外,starter模塊可以自測嗎?
          畢竟外部項目非常的復(fù)雜,starter在不同的項目集成環(huán)境中可能表現(xiàn)的不一樣,那么我們怎么進(jìn)行starter自測,來看starter在不同環(huán)境下的表現(xiàn)呢?
          我們可以通過模擬不同的運行環(huán)境,來測試starter相應(yīng)的表現(xiàn),實際就是測試相應(yīng)的自動配置有沒有生效。
          還是直接看代碼:
          class SchedulerAutoConfigurationTest {    // 通過contextRunner來模擬運行環(huán)境,這里是模擬配置了SchedulerAutoConfiguration類的應(yīng)用環(huán)境,實際也就是引用了scheduler-spring-boot-starter后生效的配置    private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()            .withConfiguration(AutoConfigurations.of(SchedulerAutoConfiguration.class));
          @Test void testAutoConfiguration() { this.contextRunner.run((context) -> { // 測試自動配置有沒有注入ScheduledThreadPoolExecutor Bean assertThat(context).hasSingleBean(ScheduledThreadPoolExecutor.class); // 測試自動配置有沒有注入SchedulerProperties Bean assertThat(context).hasSingleBean(SchedulerProperties.class); // 測試自動配置有沒有注入Scheduler Bean assertThat(context).hasSingleBean(Scheduler.class); }); }
          @Test void testProperties() { // 模擬環(huán)境配置了相應(yīng)的參數(shù) this.contextRunner.withPropertyValues("scheduler.period=5000", "scheduler.task-name=test-example") .run((context) -> { // 測試對應(yīng)參數(shù)設(shè)置是否生效 assertThat(context.getBean(SchedulerProperties.class).getPeriod()).isEqualTo(5000); assertThat(context.getBean(SchedulerProperties.class).getTaskName()).isEqualTo("test-example"); }); }}
          可以看到,通過模擬不同的集成環(huán)境,我們可以測試自定義starter在不同環(huán)境中的自動配置情況,非常的簡單高效。



          寫在最后

          我們從Spring Boot的自動配置講起,舉例一步步講解了spring-boot-autoconfigure如何與spring-boot-starter-xxx模塊配合,進(jìn)而完成自動配置的。
          我們還引出了Spring的Beans概念,代碼中的所有被Spring IoC容器所管理的實例,就叫做Beans。
          xml配置、注解配置也好,自動配置也罷,實際都是配置的Spring的Beans。
          我們還帶領(lǐng)大家一步步地完成了自定義的scheduler-spring-boot-starter開發(fā)。
          最后講解了如何通過模擬不同的集成環(huán)境,來測試自己的starter。
          希望今天的內(nèi)容對大家有所幫助,完整項目代碼請關(guān)注公眾號:WU雙,對話框回復(fù)【starter】即可獲取。
          推薦閱讀:
          《微服務(wù)不同環(huán)境到底該如何部署?最佳實踐是什么?》



          聊技術(shù),不止于技術(shù)。

          在這里我會分享技術(shù)文章、管理知識以及個人的思想感悟,歡迎點擊關(guān)注。



          瀏覽 101
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  疯狂而刺激3p视频 | 苍井空一区二区三区四区 | 国产颜射| 在线观看国产精品自拍 | 99精品视频在线观看 |