springboot starter封装
什么是springboot starter机制?
能够抛弃以前繁杂的配置,将其统一集成进starter,应用者只需要在maven中引入starter依赖,SpringBoot就能自动扫描到要加载的信息并启动相应的默认配置。
starter让我们摆脱了各种依赖库的处理,需要配置各种信息的困扰。SpringBoot会自动通过classpath路径下的类发现需要的Bean,并注册进IOC容器。SpringBoot提供了针对日常企业应用研发各种场景的spring-boot-starter依赖模块。
所有这些依赖模块都遵循着约定成俗的默认配置,并允许我们调整这些配置,即遵循“约定大于配置
”的理念。
为什么需要自定义starter?
在我们的日常开发工作中,经常会有一些独立于业务之外的配置模块,我们经常将其放到一个特定的包下,然后如果另一个工程需要复用这块功能的时候,需要将代码硬拷贝
到另一个工程,重新集成一遍,麻烦至极。 如果我们将这些可独立于业务代码之外的功配
置模块封装成一个个starter,复用
的时候只需要将其在pom中引用依赖即可,SpringBoot为我们完成自动装配,简直不要太爽。
什么时候需要自定义starter?
在我们的日常开发工作中,可能会需要开发一个通用模块,以供其它工程复用。SpringBoot就为我们提供这样的功能机制,我们可以把我们的通用模块封装成一个个starter,这样其它工程复用的时候只需要在pom中引用依赖即可,由SpringBoot为我们完成自动装配。
举一些常见场景:
通用模块-短信发送模块
基于AOP技术实现日志切面
分布式雪花ID,Long-->string,解决精度问题 jackson2/fastjson
微服务项目的数据库连接池配置
微服务项目的每个模块都要访问redis数据库,每个模块都要配置redisTemplate
自动加载核心注解说明
自定义starter的开发流程
这里以封装 liteflow-spring-boot-starter 为例
(1)创建Starter项目
命名规范
SpringBoot官方命名方式
格式:spring-boot-starter-{模块名}
举例:spring-boot-starter-web自定义命名方式
格式:{模块名}-spring-boot-starter
举例:mystarter-spring-boot-starter
引入必要的依赖
该依赖作用是在使用IDEA编写配置文件有代码提示, 作用在编译阶段
<!--表示两个项目之间依赖不传递;不设置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访问地址 ip:port/swagger-ui.html
// swagger3.0版本后,swagger ui访问地址 ip:port/swagger-ui/index.html (当前使用)
// 兼容swagger3.0, 增强型swagger knife4j ui界面: ip:port/doc.html (当前使用)
-->
<!-- knife4j提供的微服务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无需打成jar包 -->
<build>
<plugins>
</plugins>
</build>
</project>
(2)编写相关属性类
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注解基本用法
以定义一个配置信息类,和配置文件进行映射
- 前缀定义了哪些外部属性将绑定到类的字段上
- 根据 Spring Boot 宽松的绑定规则,类的属性名称必须与外部属性的名称匹配
- 我们可以简单地用一个值初始化一个字段来定义一个默认值
- 类本身可以是包私有的
- 类的字段必须有公共 setter 方法
注意:需要在配置类上加上 @EnableConfigurationProperties(LiteServiceConfig.class)
(3)编写Starter项目的业务功能
(4)编写自动配置类 xxxAutoConfiguration
命名规范: 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") // 由于这里没有启动类,注入组件不能通过@SpringBootApplication,而需通过注解扫描
//@MapperScan("com.unionman.liteflow.mapper") // 不使用@Mapper注入mapper接口,则使用包扫描
@EnableConfigurationProperties(LiteServiceConfig.class) // 激活自动配置(指定文件中的配置)
public class LiteFlowAutoConfiguration {
//
// @Bean(initMethod = "init", destroyMethod = "beforeDestory")
// public InitLiteFlowBean initLiteFlowBean(){
// return new InitLiteFlowBean();
// }
}
@Configuration
: 定义一个配置类
@EnableConfigurationProperties
:作用是使@ConfigurationProperties注解生效。
如 @EnableConfigurationProperties(LiteServiceConfig.class) 让上面的LiteServiceConfig中的
@ConfigurationProperties生效
(5)编写spring.factories文件加载自动配置类
为什么需要spring.factories?
Spring Boot会默认扫描跟启动类平级的包,如果我们的Starter跟启动类不在同一个主包下
,需要通过配置spring.factories
文件来配置生效,SpringBoot默认加载各个jar包下classpath路径的spring.factories文件,配置的key为
org.springframework.boot.autoconfigure.EnableAutoConfiguration
在 resources 目录下新建 META-INF文件夹,再创建 spring.factories 文件,加入以下内容
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.liteflowstarter.config.LiteFlowAutoConfiguration,\
注意:其中填的是配置类的全限定名,多个之间
逗号分割
,还可以 \ 进行转义即相当于去掉后面换行和空格符号
(6)maven去掉打包插件
starter 我们没有main入口,需要去除pom文件中maven打包插件spring-boot-maven-plugin,starter无需打成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)在其他项目中引用
<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 识别 不同服务的唯一标识
serviceUid: demo
(8)在starter中使用@Component等注解无法注入问题
由于这里没有启动类,注入组件不能通过@SpringBootApplication,而需通过包路径注解扫描 @ComponentScan指定starter最外层包 ,如 @ComponentScan("com.unionman.liteflow"),即扫描 该包目录下的@Component,@Repository,@Service,@Controller
@Configuration 使用详解
Full全模式和Lite轻量级模式
@Configuration参数proxyBeanMethods:
Full 全模式(默认):@Configuration(proxyBeanMethods = true)
同一配置类下,当直接调用@Bean修饰的方法注入的对象,则调用该方法会被代理
,从ioc容器中取bean实列,所以实例是一样的。即单实例对象,在该模式下SpringBoot每次启动都会判断检查容器中是否存在该组件
Lite 轻量级模式:@Configuration(proxyBeanMethods = false)
同一配置类下,当直接调用@Bean修饰的方法注入的对象,则调用该方法不会被代理
,相当于直接调用一个普通方法,会有构造方法,但是没有bean的生命周期,返回的是不同的实例。
注:proxyBeanMethods 是为了让使用@Bean注解的方法被代理。而不是@Bean的单例多例的设置参数。
测试例子这里不展示,可以下载我的代码查看
@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()方法不代理,直接调用
//注意:@Configuration(proxyBeanMethods = true):myBean()方法代理,从ioc容器拿
return new YourBean(myBean());
}
}
什么时候用Full全模式,什么时候用Lite轻量级模式?
当在你的同一个Configuration配置类中,注入到容器中的bean实例之间有依赖关系
时,建议使用Full全模式
当在你的同一个Configuration配置类中,注入到容器中的bean实例之间没有依赖关系
时,建议使用Lite轻量级
模式,以提高springboot的启动速度和性能
@ComponentScan 使用详解
原文链接:https://blog.csdn.net/u012326462/article/details/82765485
作用就是根据定义的扫描路径
,把符合扫描规则
的类装配
到spring容器
中。
如果@ComponentScan配置了包,则扫描@ComponentScan配置的包
如果@ComponentScan没有配置包,则扫描其注解的类的包(如 @SpringbootApplication内部就有@ComponentScan,则扫描启动类所在的包,这就是为什么只要在该包下基本都能被扫到的原因)
//
// 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: 用于指定包的路径,进行扫描
basePackageClasses: 用于指定某个类所在的包的路径进行扫描
nameGenerator: bean的名称的生成器
useDefaultFilters: 是否开启对@Component,@Repository,@Service,@Controller的类进行检测,默认true
includeFilters: 包含的过滤条件
FilterType.ANNOTATION:按照注解过滤
FilterType.ASSIGNABLE_TYPE:按照给定的类型
FilterType.ASPECTJ:使用ASPECTJ表达式
FilterType.REGEX:正则
FilterType.CUSTOM:自定义规则
excludeFilters: 排除的过滤条件,用法和includeFilters一样
DEMO项目目录
应用默认的过滤器,扫描service包:
@Configuration
@ComponentScan(value = "com.xhx.spring.service",
useDefaultFilters = true
)
public class MyConfig {
}
HelloController所在的包的类也被扫描了进去
@Configuration
@ComponentScan(value = "com.xhx.spring.service",
useDefaultFilters = true,
basePackageClasses = HelloController.class
)
public class MyConfig {
}
把默认的过滤器关掉,扫描带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 {
}
自定义扫描过滤器
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的类 会被扫描
if(className.contains("Controller")){
return true;
}
return false;
}
}
注意: 要设置useDefaultFilters = false(系统默认为true,需要手动设置) includeFilters包含过滤规则才会生效。
@Configuration
@ComponentScan(value = "com.xhx.spring",
useDefaultFilters = false,
includeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM,classes = {MyTypeFilter.class})
}
)
public class MyConfig {
}
输出spring容器中的bean的测试类:只过滤输出了名字中含有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自定义多数据源starter组件
https://blog.csdn.net/weixin_45840947/article/details/123892165#t0
【有空看看】mybatis-spring-boot-starter
mybatis自动配置原理
https://jishuin.proginn.com/p/763bfbd5ad27