实现一个简单的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.项目中引入
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.测试
修改配置enable: false
一直调用成功
``