- 想要自定义starter,首先要了解springboot是如何加载starter的,也就是springboot的自动装配机制
starter加载原理:
springboot通过一个
@SpringBootApplication
注解启动项目,springboot在项目启动的时候,会将项目中所有声明为Bean对象(注解、xml)的实例信息全部加载到ioc容器当中。除此之外也会将所有依赖到的starter里的bean信息加载到ioc容器中,从而做到所谓的零配置,开箱即用。
@EnableAutoConfiguration注解进行加载starter,@EnableAutoConfiguration在
@SpringBootApplication
注解里面具体的加载实现是由
@EnableAutoConfiguration
注解下import了一个AutoConfigurationImportSelector
加载器实现这个
AutoConfigurationImportSelector
会去所引用的依赖jar包下,找到一个 spring.factories 文件,一般spring.factories文件里都会声明该依赖所提供的核心功能bean配置
信息。文件一般在依赖jar包的META-INF
文件夹下面
自定义starter:
- 了解了springboot加载starter原理,其实就是加载
依赖jar包下的spring.factories
文件。所以我们要自定义starter
,就需要- 在项目中建立一个META-INF的文件夹
- 然后在文件夹下面建一个
spring.factories
文件,文件里将你需要提供出去的bean实例信息配置好就行。
- resources文件夹下面新建META-INF文件夹,并新建spring.factories文件
- 然后在文件夹下面建一个
- 需要先删掉启动类,因为自定义的starter是不能有启动入口的
- 删掉pom.xml中的spring-boot-maven-plugin插件,如果没引入该插件,请忽略。 自动引入
spring-boot-maven-plugin
插件,如果删掉启动类话,进行install的时候,会报找不到Unable to find main class
(找不到主类错误),这时候只需要删掉这个插件再进行install即可mvn clean install
- 删掉pom.xml中的spring-boot-maven-plugin插件,如果没引入该插件,请忽略。 自动引入
自定义starter的开发流程:
- 创建Starter项目
- 命名规范
{模块名}-spring-boot-starter
- 必须引入的依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
- 命名规范
- 定义Starter需要的配置类(Properties)
- 编写相关属性类(XxxProperties):
SocketProperties.java
- 编写相关属性类(XxxProperties):
- 编写Starter项目的业务功能
- 编写自动配置类
- 编写spring.factories文件加载自动配置类
- 打包安装
- 其它项目引用
常见场景:
- 通用模块-短信发送模块
- 基于AOP技术实现日志切面
- 分布式雪花ID,
Long-->string
,解决精度问题 - 微服务项目的数据库连接池配置
- 微服务项目的每个模块都要访问redis数据库,每个模块都要配置redisTemplate 也可以通过starter解决
AOP方式统一服务
- @ConfigurationProperties注解基本用法
- 前缀定义了哪些外部属性将绑定到类的字段上
- 根据 Spring Boot 宽松的绑定规则,类的属性名称必须与外部属性的名称匹配
- 我们可以简单地用一个值初始化一个字段来定义一个默认值
- 类本身可以是包私有的
- 类的字段必须有公共 setter 方法
- @Configuration: 定义一个配置类
- @EnableConfigurationProperties:
@EnableConfigurationProperties
注解的作用是@ConfigurationProperties
注解生效。- 如果只配置@ConfigurationProperties注解,在IOC容器中是获取不到properties配置文件转化的bean的
public @interface ConditionalOnProperty
/**
* The string representation of the expected value for the properties. If not
* specified, the property must <strong>not</strong> be equal to {@code false}.
* @return the expected value
*/
String havingValue() default "";
/**
* Specify if the condition should match if the property is not set. Defaults to
* {@code false}.
* @return if the condition should match if the property is missing
*/
boolean matchIfMissing() default false;
- @ConditionalOnProperty(prefix = "skt.msg",value = "enabled", matchIfMissing = true): matchIfMissing属性:默认情况下
matchIfMissing为false
,也就是说如果未进行属性配置,则自动配置不生效。如果matchIfMissing为true
,则表示如果没有对应的属性配置,则自动配置默认生效 - @ConditionalOnMissingBean: 在@bean定义上,它的作用就是在容器加载它作用的bean时,检查容器中是否存在目标类型
(ConditionalOnMissingBean注解的value值)
的bean了,如果存在这跳过原始bean的BeanDefinition加载动作。
- 导入依赖
<!--表示两个项目之间依赖不传递;不设置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>
2.编写相关属性类(XxxProperties):MsgProperties.java
package com.osvue.env.app.props;
import java.io.Serializable;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
*
<p>
相关属性类(XxxProperties):MsgProperties.java
--@ConfigurationProperties注解基本用法
前缀定义了哪些外部属性将绑定到类的字段上
根据 Spring Boot 宽松的绑定规则,类的属性名称必须与外部属性的名称匹配
我们可以简单地用一个值初始化一个字段来定义一个默认值
类本身可以是包私有的
类的字段必须有公共 setter 方法
注意:SmsProperties代码写完后会报如下错误,这是正常的,因为
还有配置类AutoConfig和一个注解@EnableConfigurationProperties没有加
</p>
* @ClassName: MsgProperties
* @Description:TODO(这里用一句话描述这个类的作用)
* @author: hzq
* @date: 2023-5-19 15:17:45
* @Copyright: 2023
*/
@ConfigurationProperties(prefix ="skt.msg")
public class MsgProperties implements Serializable{
/**
* @Fields serialVersionUID : TODO(用一句话描述这个变量表示什么)
*/
private static final long serialVersionUID = 1L;
/**
* 是否启用
*/
private Boolean enabled;
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
}
- 编写Starter项目的业务功能
package com.osvue.env.app.aspect;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import lombok.extern.slf4j.Slf4j;
/**
*
* @ClassName: WebLogAspect
* @Description:TODO(切面)
* @author: hzq
* @date: 2023-5-19 15:20:23
* @Copyright: 2023
*/
@Aspect
@Component
@Slf4j
public class WebLogAspect {
@Pointcut("execution(* *..*Controller.*(..))")
public void webLog(){}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录下请求内容
log.info("开始服务:{}", request.getRequestURL().toString());
log.info("客户端IP :{}" , request.getRemoteAddr());
log.info("参数值 :{}", Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
log.info("返回值 : {}" , ret);
}
/**
*
* @Title: logAfterThrowing
* @Description: TODO(异常)
* @param: @param joinPoint
* @param: @param error
* @return: void
* @throws
*/
@AfterThrowing(pointcut = "webLog()", throwing = "error")
public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
log.info("logAfterThrowing() is running!");
log.info("getName() : " + joinPoint.getSignature().getName());
log.info("Exception : " + error);
log.info("******");
}
}
- 编写自动配置类AutoConfig
package com.osvue.env.app.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.osvue.env.app.aspect.WebLogAspect;
import com.osvue.env.app.props.MsgProperties;
/**
*
* @ClassName: WebLogAutoConfig
* @Description:TODO(自动配置类)
* @author: hzq
* @date: 2023-5-19 15:24:59
* @Copyright: 2023
*/
@Configuration
@EnableConfigurationProperties({MsgProperties.class})
@ConditionalOnProperty(prefix = "skt.msg",value = "enabled",matchIfMissing = true)
public class WebLogAutoConfig {
@Bean
@ConditionalOnMissingBean
public WebLogAspect webLogAspect(){
return new WebLogAspect();
}
}
- 编写
spring.factories
文件加载自动配置类 - 在
resources
下新建META-INF
文件夹,然后创建spring.factories
文件 - 在该文件中加入如下配置,该配置指定上步骤中定义的配置类为自动装配的配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.osvue.env.app.config.WebLogAutoConfig
打包安装
导入使用
<dependency>
<groupId>com.osvue</groupId>
<artifactId>socketmsg-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>