实现一个简单的SpringbootStarter

实现一个简单的Springboot-Starter

根据Spring提供的Factories机制,实现一个Springboot-Starter流程如下:
1.在项目中提供resource/META-INF/spring.factories文件,文件中声明key值org.springframework.boot.autoconfigure.EnableAutoConfiguration所对应的value值,这里value值一般是自己实现的starter组件中的配置类。

2.在配置类中可以使用@ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnClass、@ConditionalOnMissingClass、@ConditionalOnProperty等一系列注解来Bean需要满足的条件。

接下来以借助google的guava包实现一个简单的限流器为例,介绍一下如何实现一个自己的Springboot-Starter。

POM文件如下:

 <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.5.RELEASE</version>
    <relativePath/>
  </parent>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
      <groupId>commons-beanutils</groupId>
      <artifactId>commons-beanutils</artifactId>
      <version>1.8.3</version>
    </dependency>
    <!-- RateLimiter -->
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>18.0</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.75</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <finalName>ratelimiter-spring-boot-starter</finalName>
    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
        <includes>
          <include>**/**</include>
        </includes>
      </resource>
    </resources>
    <testResources>
      <testResource>
        <directory>src/test/resources</directory>
        <filtering>true</filtering>
        <includes>
          <include>**/**</include>
        </includes>
      </testResource>
    </testResources>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.12.4</version>
        <configuration>
          <skipTests>true</skipTests>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <version>2.5</version>
        <configuration>
          <encoding>${project.build.sourceEncoding}</encoding>
        </configuration>
      </plugin>
      <!-- 将byte-buddy打包到Agent中 -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <artifactSet>
            <includes>
              <include>com.google.guava:guava:jar:</include>
            </includes>
          </artifactSet>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>${project.build.sourceEncoding}</encoding>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-source-plugin</artifactId>
        <version>2.1.2</version>
        <executions>
          <execution>
            <id>attach-sources</id>
            <goals>
              <goal>jar</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

guava中的RateLimiter是令牌桶算法的一种实现,令牌桶算法和漏桶算法的主要思想以及RateLimiter的使用方法可以自行了解。

1.自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DoRateLimiter {
    double permitsPerSecond() default 0D;//最大流量限制
    String returnJson() default "";//调用失败的返回结果
}

2.限流器的具体逻辑

public interface IValveService {
    Object access(ProceedingJoinPoint jp, Method method, DoRateLimiter doRateLimiter, Object[] args) throws Throwable;
}
public class RateLimiterValve implements IValveService {
    //借助aspectj提供的动态代理实现
    @Override
    public Object access(ProceedingJoinPoint jp, Method method, DoRateLimiter doRateLimiter, Object[] args) throws Throwable {
        if(doRateLimiter.permitsPerSecond() == 0)return jp.proceed();//为0表示不启用限流器
        String clazzName = jp.getTarget().getClass().getName();
        String methodName = method.getName();
        String key = clazzName + "." + methodName;//类名+方法名作为key值
        if (null == Constants.rateLimiterMap.get(key)){
           //如果方法还未创建对应的限流器,则创建后缓存 Constants.rateLimiterMap.put(key,RateLimiter.create(doRateLimiter.permitsPerSecond()));
        }
        RateLimiter rateLimiter = Constants.rateLimiterMap.get(key);
        //获取令牌,能够得到则允许访问
        if (rateLimiter.tryAcquire()){
            return jp.proceed();
        }
        //无法获得令牌,返回失败结果
        return JSON.parseObject(doRateLimiter.returnJson(),method.getReturnType());
    }
}
public class Constants {
    public static Map<String, RateLimiter> rateLimiterMap = Collections.synchronizedMap(new HashMap<String, RateLimiter>());
}

3.定义切面类

切点表达式不熟悉的可以参考:https://zhuanlan.zhihu.com/p/153317556

@Aspect
//@Component
public class DoRateLimiterPoint {
    @Pointcut("@annotation(edu.nuaa.middle.ratelimiter.annotation.DoRateLimiter)")
    public void aopPoint() {
    }

    @Around("aopPoint() && @annotation(doRateLimiter)")
    public Object doRouter(ProceedingJoinPoint jp, DoRateLimiter doRateLimiter) throws Throwable {
        IValveService valveService = new RateLimiterValve();
        return valveService.access(jp, getMethod(jp), doRateLimiter, jp.getArgs());
    }

    private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
        Signature sig = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) sig;
        return jp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
    }

}

4.定义配置类

@Configuration
@EnableConfigurationProperties(EnableRateLimiterProperties.class)
@ConditionalOnProperty(prefix = "zk.ratelimiter",name = "enable",havingValue = "true")
public class MyRateLimiterAutoConfiguration {
    @Bean
    public DoRateLimiterPoint doRateLimiterPoint(){
        return new DoRateLimiterPoint();
    }
}
@ConfigurationProperties("zk.ratelimiter")
public class EnableRateLimiterProperties {
    private boolean enable;

    public boolean isEnable() {
        return enable;
    }

    public void setEnable(boolean enable) {
        this.enable = enable;
    }
}

5.定义Factories文件

在这里插入图片描述

org.springframework.boot.autoconfigure.EnableAutoConfiguration=edu.nuaa.middle.ratelimiter.config.MyRateLimiterAutoConfiguration

6.项目中引入

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hw0DjzmG-1688734315346)(H:\杂项软件\Typora\image\image-20230707204332021.png)]

在这里插入图片描述

application.yml文件

server:
  port: 8081
zk:
  ratelimiter:
    enable: true //true表示开启,这个项目实现采用配置文件启停。当然也可以使用注解启停,有需要可以自己查找资料了解一下,不懂的话可以留言。
@RestController
public class UserController {
    private Logger logger = LoggerFactory.getLogger(UserController.class);

    /**
     * 测试:http://localhost:8081/api/queryUserInfo?userId=aaa
     */
    @DoRateLimiter(permitsPerSecond = 1, returnJson = "{\"code\":\"1111\",\"info\":\"调用方法超过最大次数,限流返回!\"}")
    @RequestMapping(path = "/api/queryUserInfo", method = RequestMethod.GET)
    public UserInfo queryUserInfo(@RequestParam String userId) throws InterruptedException {
        logger.info("查询用户信息,userId:{}", userId);
        return new UserInfo("虫虫:" + userId, 19, "天津市东丽区万科赏溪苑14-0000");
    }

}
public class UserInfo {
    //code、info可以统一定义一个类
    private String code;
    private String info;

    private String name;
    private Integer age;
    private String address;

    public UserInfo() {

    }

    public UserInfo(String code, String info) {
        this.code = code;
        this.info = info;
    }

    public UserInfo(String name, Integer age, String address) {
        this.code = "0000";
        this.info = "success";
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

}

7.测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3sHnufDN-1688734315349)(H:\杂项软件\Typora\image\image-20230707204830789.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VjgzL3sz-1688734315350)(H:\杂项软件\Typora\image\image-20230707204819870.png)]

修改配置enable: false

一直调用成功
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zJL6tqds-1688734315353)(H:\杂项软件\Typora\image\image-20230707204944996.png)]
``

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

可怕的竹鼠商

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值