• 想要自定义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,就需要
      1. 在项目中建立一个META-INF的文件夹
      1. 然后在文件夹下面建一个spring.factories文件,文件里将你需要提供出去的bean实例信息配置好就行。
      • resources文件夹下面新建META-INF文件夹,并新建spring.factories文件
      1. 需要先删掉启动类,因为自定义的starter是不能有启动入口的
      1. 删掉pom.xml中的spring-boot-maven-plugin插件,如果没引入该插件,请忽略。 自动引入spring-boot-maven-plugin插件,如果删掉启动类话,进行install的时候,会报找不到Unable to find main class(找不到主类错误),这时候只需要删掉这个插件再进行install即可 mvn clean install

自定义starter的开发流程:

  1. 创建Starter项目
    1. 命名规范 {模块名}-spring-boot-starter
    2. 必须引入的依赖
    3.   <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-configuration-processor</artifactId>
           <optional>true</optional>
       </dependency> 
      
  2. 定义Starter需要的配置类(Properties)
    1. 编写相关属性类(XxxProperties):SocketProperties.java
  3. 编写Starter项目的业务功能
  4. 编写自动配置类
  5. 编写spring.factories文件加载自动配置类
  6. 打包安装
  7. 其它项目引用

常见场景:

  1. 通用模块-短信发送模块
  2. 基于AOP技术实现日志切面
  3. 分布式雪花ID,Long-->string,解决精度问题
  4. 微服务项目的数据库连接池配置
  5. 微服务项目的每个模块都要访问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加载动作。
  1. 导入依赖
<!--表示两个项目之间依赖不传递;不设置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;
	}
	
	
	

}

  1. 编写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("******");
	 
		}
}

  1. 编写自动配置类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();
    }

}

  1. 编写spring.factories文件加载自动配置类
  2. resources下新建META-INF文件夹,然后创建spring.factories文件
  3. 在该文件中加入如下配置,该配置指定上步骤中定义的配置类为自动装配的配置

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.osvue.env.app.config.WebLogAutoConfig
  1. 打包安装

  2. 导入使用

<dependency>
	<groupId>com.osvue</groupId>
	<artifactId>socketmsg-spring-boot-starter</artifactId>
	<version>0.0.1-SNAPSHOT</version>
</dependency>