springboot starter封裝
什么是springboot starter機(jī)制箕慧?
能夠拋棄以前繁雜的配置偏螺,將其統(tǒng)一集成進(jìn)starter,應(yīng)用者只需要在maven中引入starter依賴,SpringBoot就能自動(dòng)掃描到要加載的信息并啟動(dòng)相應(yīng)的默認(rèn)配置状知。
starter讓我們擺脫了各種依賴庫(kù)的處理,需要配置各種信息的困擾孽查。SpringBoot會(huì)自動(dòng)通過(guò)classpath路徑下的類發(fā)現(xiàn)需要的Bean饥悴,并注冊(cè)進(jìn)IOC容器。SpringBoot提供了針對(duì)日常企業(yè)應(yīng)用研發(fā)各種場(chǎng)景的spring-boot-starter依賴模塊盲再。
所有這些依賴模塊都遵循著約定成俗的默認(rèn)配置西设,并允許我們調(diào)整這些配置,即遵循“約定大于配置
”的理念答朋。
為什么需要自定義starter?
在我們的日常開發(fā)工作中梦碗,經(jīng)常會(huì)有一些獨(dú)立于業(yè)務(wù)之外的配置模塊禽绪,我們經(jīng)常將其放到一個(gè)特定的包下蓖救,然后如果另一個(gè)工程需要復(fù)用這塊功能的時(shí)候,需要將代碼硬拷貝
到另一個(gè)工程印屁,重新集成一遍循捺,麻煩至極。 如果我們將這些可獨(dú)立于業(yè)務(wù)代碼之外的功配
置模塊封裝成一個(gè)個(gè)starter雄人,復(fù)用
的時(shí)候只需要將其在pom中引用依賴即可从橘,SpringBoot為我們完成自動(dòng)裝配,簡(jiǎn)直不要太爽。
什么時(shí)候需要自定義starter?
在我們的日常開發(fā)工作中署辉,可能會(huì)需要開發(fā)一個(gè)通用模塊静浴,以供其它工程復(fù)用。SpringBoot就為我們提供這樣的功能機(jī)制挟阻,我們可以把我們的通用模塊封裝成一個(gè)個(gè)starter,這樣其它工程復(fù)用的時(shí)候只需要在pom中引用依賴即可,由SpringBoot為我們完成自動(dòng)裝配驻民。
舉一些常見場(chǎng)景:
通用模塊-短信發(fā)送模塊
基于AOP技術(shù)實(shí)現(xiàn)日志切面
分布式雪花ID,Long-->string履怯,解決精度問(wèn)題 jackson2/fastjson
微服務(wù)項(xiàng)目的數(shù)據(jù)庫(kù)連接池配置
微服務(wù)項(xiàng)目的每個(gè)模塊都要訪問(wèn)redis數(shù)據(jù)庫(kù)回还,每個(gè)模塊都要配置redisTemplate
自動(dòng)加載核心注解說(shuō)明
自定義starter的開發(fā)流程
這里以封裝 liteflow-spring-boot-starter 為例
(1)創(chuàng)建Starter項(xiàng)目
命名規(guī)范
SpringBoot官方命名方式
格式:spring-boot-starter-{模塊名}
舉例:spring-boot-starter-web自定義命名方式
格式:{模塊名}-spring-boot-starter
舉例:mystarter-spring-boot-starter
引入必要的依賴
該依賴作用是在使用IDEA編寫配置文件有代碼提示, 作用在編譯階段
<!--表示兩個(gè)項(xiàng)目之間依賴不傳遞;不設(shè)置optional或者optional是false叹洲,表示傳遞依賴-->
<!--例如:project1依賴a.jar(optional=true),project2依賴project1,則project2不依賴a.jar-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional> <!-- 避免傳遞依賴 -->
</dependency>
完整pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.unionman</groupId>
<artifactId>liteflow-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>liteflow-spring-boot-starter</name>
<description>liteflow-spring-boot-starter</description>
<properties>
<java.version>1.8</java.version>
<swagger.fox.version>3.0.0</swagger.fox.version>
<knife4j.version>2.0.8</knife4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--非必需,該依賴作用是在使用IDEA編寫配置文件有代碼提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-test</artifactId>-->
<!-- <scope>test</scope>-->
<!-- </dependency>-->
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-spring-boot-starter</artifactId>
<version>2.8.3</version>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-script-groovy</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.baomidou</groupId>-->
<!-- <artifactId>mybatis-plus-boot-starter-test</artifactId>-->
<!-- <version>3.5.1</version>-->
<!-- </dependency>-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${swagger.fox.version}</version>
</dependency>
<!--
// swagger2.0版本后柠硕,swagger ui訪問(wèn)地址 ip:port/swagger-ui.html
// swagger3.0版本后,swagger ui訪問(wèn)地址 ip:port/swagger-ui/index.html (當(dāng)前使用)
// 兼容swagger3.0, 增強(qiáng)型swagger knife4j ui界面: ip:port/doc.html (當(dāng)前使用)
-->
<!-- knife4j提供的微服務(wù)starter -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!-- knife4j ui -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-micro-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
</dependency>
</dependencies>
<!-- starter 我們沒有main入口运提,需要去除pom文件中maven打包插件spring-boot-maven-plugin,starter無(wú)需打成jar包 -->
<build>
<plugins>
</plugins>
</build>
</project>
(2)編寫相關(guān)屬性類
package com.unionman.liteflow.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "app.service")
@Data
public class LiteServiceConfig {
private String serviceUid;
}
@ConfigurationProperties注解基本用法
以定義一個(gè)配置信息類蝗柔,和配置文件進(jìn)行映射
- 前綴定義了哪些外部屬性將綁定到類的字段上
- 根據(jù) Spring Boot 寬松的綁定規(guī)則,類的屬性名稱必須與外部屬性的名稱匹配
- 我們可以簡(jiǎn)單地用一個(gè)值初始化一個(gè)字段來(lái)定義一個(gè)默認(rèn)值
- 類本身可以是包私有的
- 類的字段必須有公共 setter 方法
注意:需要在配置類上加上 @EnableConfigurationProperties(LiteServiceConfig.class)
(3)編寫Starter項(xiàng)目的業(yè)務(wù)功能
(4)編寫自動(dòng)配置類 xxxAutoConfiguration
命名規(guī)范: XXXAutoConfiguration民泵,如 LiteFlowAutoConfiguration
package com.unionman.liteflow.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration // 表示該類是配置類
@ComponentScan("com.unionman.liteflow") // 由于這里沒有啟動(dòng)類癣丧,注入組件不能通過(guò)@SpringBootApplication,而需通過(guò)注解掃描
//@MapperScan("com.unionman.liteflow.mapper") // 不使用@Mapper注入mapper接口栈妆,則使用包掃描
@EnableConfigurationProperties(LiteServiceConfig.class) // 激活自動(dòng)配置(指定文件中的配置)
public class LiteFlowAutoConfiguration {
//
// @Bean(initMethod = "init", destroyMethod = "beforeDestory")
// public InitLiteFlowBean initLiteFlowBean(){
// return new InitLiteFlowBean();
// }
}
@Configuration
: 定義一個(gè)配置類
@EnableConfigurationProperties
:作用是使@ConfigurationProperties注解生效胁编。
如 @EnableConfigurationProperties(LiteServiceConfig.class) 讓上面的LiteServiceConfig中的
@ConfigurationProperties生效
(5)編寫spring.factories文件加載自動(dòng)配置類
為什么需要spring.factories?
Spring Boot會(huì)默認(rèn)掃描跟啟動(dòng)類平級(jí)的包鳞尔,如果我們的Starter跟啟動(dòng)類不在同一個(gè)主包下
嬉橙,需要通過(guò)配置spring.factories
文件來(lái)配置生效,SpringBoot默認(rèn)加載各個(gè)jar包下classpath路徑的spring.factories文件寥假,配置的key為
org.springframework.boot.autoconfigure.EnableAutoConfiguration
在 resources 目錄下新建 META-INF文件夾憎夷,再創(chuàng)建 spring.factories 文件,加入以下內(nèi)容
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.liteflowstarter.config.LiteFlowAutoConfiguration,\
注意:其中填的是配置類的全限定名昧旨,多個(gè)之間
逗號(hào)分割
拾给,還可以 \ 進(jìn)行轉(zhuǎn)義即相當(dāng)于去掉后面換行和空格符號(hào)
(6)maven去掉打包插件
starter 我們沒有main入口祥得,需要去除pom文件中maven打包插件spring-boot-maven-plugin,starter無(wú)需打成jar包
<build>
<plugins>
<plugin>
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-maven-plugin</artifactId>-->
<!-- <version>2.3.1.RELEASE</version>-->
</plugin>
</plugins>
</build>
(7)在其他項(xiàng)目中引用
<dependency>
<groupId>com.unionman</groupId>
<artifactId>liteflow-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
配置文件application.yml 添加starter所需的配置
liteflow:
ruleSource: liteflow/*.el.xml
app:
service:
# umliteflow-spring-boot-starter 識(shí)別 不同服務(wù)的唯一標(biāo)識(shí)
serviceUid: demo
(8)在starter中使用@Component等注解無(wú)法注入問(wèn)題
由于這里沒有啟動(dòng)類,注入組件不能通過(guò)@SpringBootApplication蒋得,而需通過(guò)包路徑注解掃描 @ComponentScan指定starter最外層包 级及,如 @ComponentScan("com.unionman.liteflow"),即掃描 該包目錄下的@Component额衙,@Repository饮焦,@Service,@Controller
@Configuration 使用詳解
Full全模式和Lite輕量級(jí)模式
@Configuration參數(shù)proxyBeanMethods:
Full 全模式(默認(rèn)):@Configuration(proxyBeanMethods = true)
同一配置類下窍侧,當(dāng)直接調(diào)用@Bean修飾的方法注入的對(duì)象县踢,則調(diào)用該方法會(huì)被代理
,從ioc容器中取bean實(shí)列伟件,所以實(shí)例是一樣的硼啤。即單實(shí)例對(duì)象,在該模式下SpringBoot每次啟動(dòng)都會(huì)判斷檢查容器中是否存在該組件
Lite 輕量級(jí)模式:@Configuration(proxyBeanMethods = false)
同一配置類下斧账,當(dāng)直接調(diào)用@Bean修飾的方法注入的對(duì)象谴返,則調(diào)用該方法不會(huì)被代理
,相當(dāng)于直接調(diào)用一個(gè)普通方法咧织,會(huì)有構(gòu)造方法嗓袱,但是沒有bean的生命周期,返回的是不同的實(shí)例习绢。
注:proxyBeanMethods 是為了讓使用@Bean注解的方法被代理渠抹。而不是@Bean的單例多例的設(shè)置參數(shù)。
測(cè)試?yán)舆@里不展示闪萄,可以下載我的代碼查看
@Configuration(proxyBeanMethods = false)
public class AppConfig {
//放一份myBean到ioc容器
@Bean
public Mybean myBean() {
return new Mybean();
}
//放一份yourBean到ioc容器
@Bean
public YourBean yourBean() {
System.out.println("==========");
//注意:@Configuration(proxyBeanMethods = false):myBean()方法不代理梧却,直接調(diào)用
//注意:@Configuration(proxyBeanMethods = true):myBean()方法代理,從ioc容器拿
return new YourBean(myBean());
}
}
什么時(shí)候用Full全模式桃煎,什么時(shí)候用Lite輕量級(jí)模式?
當(dāng)在你的同一個(gè)Configuration配置類中大刊,注入到容器中的bean實(shí)例之間有依賴關(guān)系
時(shí)为迈,建議使用Full全模式
當(dāng)在你的同一個(gè)Configuration配置類中,注入到容器中的bean實(shí)例之間沒有依賴關(guān)系
時(shí)缺菌,建議使用Lite輕量級(jí)
模式葫辐,以提高springboot的啟動(dòng)速度和性能
@ComponentScan 使用詳解
原文鏈接:https://blog.csdn.net/u012326462/article/details/82765485
作用就是根據(jù)定義的掃描路徑
,把符合掃描規(guī)則
的類裝配
到spring容器
中伴郁。
如果@ComponentScan配置了包耿战,則掃描@ComponentScan配置的包
如果@ComponentScan沒有配置包,則掃描其注解的類的包(如 @SpringbootApplication內(nèi)部就有@ComponentScan焊傅,則掃描啟動(dòng)類所在的包剂陡,這就是為什么只要在該包下基本都能被掃到的原因)
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.annotation.AliasFor;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default "**/*.class";
boolean useDefaultFilters() default true;
ComponentScan.Filter[] includeFilters() default {};
ComponentScan.Filter[] excludeFilters() default {};
boolean lazyInit() default false;
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}
basePackages與value: 用于指定包的路徑狈涮,進(jìn)行掃描
basePackageClasses: 用于指定某個(gè)類所在的包的路徑進(jìn)行掃描
nameGenerator: bean的名稱的生成器
useDefaultFilters: 是否開啟對(duì)@Component,@Repository鸭栖,@Service歌馍,@Controller的類進(jìn)行檢測(cè),默認(rèn)true
includeFilters: 包含的過(guò)濾條件
FilterType.ANNOTATION:按照注解過(guò)濾
FilterType.ASSIGNABLE_TYPE:按照給定的類型
FilterType.ASPECTJ:使用ASPECTJ表達(dá)式
FilterType.REGEX:正則
FilterType.CUSTOM:自定義規(guī)則
excludeFilters: 排除的過(guò)濾條件晕鹊,用法和includeFilters一樣
DEMO項(xiàng)目目錄
應(yīng)用默認(rèn)的過(guò)濾器松却,掃描service包:
@Configuration
@ComponentScan(value = "com.xhx.spring.service",
useDefaultFilters = true
)
public class MyConfig {
}
HelloController所在的包的類也被掃描了進(jìn)去
@Configuration
@ComponentScan(value = "com.xhx.spring.service",
useDefaultFilters = true,
basePackageClasses = HelloController.class
)
public class MyConfig {
}
把默認(rèn)的過(guò)濾器關(guān)掉,掃描帶Controller注解的溅话。
@Configuration
@ComponentScan(value = "com.xhx.spring",
useDefaultFilters = false,
includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
}
)
public class MyConfig {
}
按照類的類型掃描晓锻,雖然HelloController沒有加注解,但是被注入到了spring容器中
@Configuration
@ComponentScan(value = "com.xhx.spring",
useDefaultFilters = false,
includeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {HelloController.class})
}
)
public class MyConfig {
}
自定義掃描過(guò)濾器
package com.xhx.spring.componentscan.config;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import java.io.IOException;
/**
* xuhaixing
* 2018/9/18 23:07
**/
public class MyTypeFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
// 滿足類名包含Controller的類 會(huì)被掃描
if(className.contains("Controller")){
return true;
}
return false;
}
}
注意: 要設(shè)置useDefaultFilters = false(系統(tǒng)默認(rèn)為true飞几,需要手動(dòng)設(shè)置) includeFilters包含過(guò)濾規(guī)則才會(huì)生效砚哆。
@Configuration
@ComponentScan(value = "com.xhx.spring",
useDefaultFilters = false,
includeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM,classes = {MyTypeFilter.class})
}
)
public class MyConfig {
}
輸出spring容器中的bean的測(cè)試類:只過(guò)濾輸出了名字中含有hello的類。
package com.xhx.spring.componentscan;
import com.xhx.spring.componentscan.config.MyConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ComponentScanApplicationTests {
@Test
public void testLoads() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
List<String> hello = Arrays.stream(context.getBeanDefinitionNames()).collect(Collectors.toList());
hello.stream().filter(name->name.contains("hello")).peek(System.out::println).count();
}
}
[demo]SpringBoot自定義多數(shù)據(jù)源starter組件
https://blog.csdn.net/weixin_45840947/article/details/123892165#t0
【有空看看】mybatis-spring-boot-starter
mybatis自動(dòng)配置原理
https://jishuin.proginn.com/p/763bfbd5ad27