在我们开发的时候,如果用到一个功能,我们只需要导入starter依赖,就能够利用springboot的自动装配功能,使用starter包中的功能了.对应的我们也能自己实现一个starter来使用.
越是使用简单的框架越是有很多的约定,俗称:约定大于配置;
自动装配原理
1、springboot在启动的时候会去starter的包中寻找resources/META-INF/spring.factories文件,并读取文件中 key=org.springframework.boot.autoconfigure.EnableAutoConfiguration
的所有value的AutoConfigure类进行加载.
2、在加载AutoConfigure的时候,会有一系列的@Conditional注解
,满足条件就会自动配置,并把Bean注入到Spring容器中.也可以通过@ImportAutoConfiguration
指定自动装配的类
3、Spring官方的starter一般采取spring-boot-starter-{name}
的命名方式,比如spring-boot-starter-aop.非官方的starter,官方的建议artifactId命名应该遵循{name}-spring-boot-starter
的格式.
项目结构
依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.itpluto.demo</groupId>
<artifactId>list-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- 用到了aop的拦截 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
<index>true</index>
<manifest>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
<manifestEntries>
<Implementation-Build>${maven.build.timestamp}</Implementation-Build>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoList {
String key() default "";
String returnJson() default "";
}
自动装配需要的类和配置文件
1、在resources文件下创建一个META-INF文件夹,并创建spring.factories文件.
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.itpluto.demo.config.StarterAutoConfigure
2、创建StarterAutoConfigure类
@Configuration
@ConditionalOnClass(StarterService.class)
@EnableConfigurationProperties(StarterServiceProperties.class)
public class StarterAutoConfigure {
// StarterServiceProperties 用来读取配置文件中
@Autowired
private StarterServiceProperties properties;
// 把读取到的白名单存储在 StarterService里
@Bean
@ConditionalOnMissingBean //当没有这个Bean的时候才会创建
// 当有以itpluto开头的配置,并且enabled的属性是ture的时候才生效
@ConditionalOnProperty(prefix = "itpluto.door", value = "enabled", havingValue = "true")
StarterService starterService() {
return new StarterService(properties.getUserStr());
}
// doJoinPoint aop拦截,这里注册到Spring容器中
@Bean
@ConditionalOnMissingBean
DoJoinPoint doJoinPoint(){
return new DoJoinPoint();
}
}
3、创建Properties类,用来yaml配置文件和属性进行绑定
@ConfigurationProperties("itpluto.door")
public class StarterServiceProperties {
private String userStr;
public String getUserStr() {
return userStr;
}
public void setUserStr(String userStr) {
this.userStr = userStr;
}
}
4、创建StarterService类,用来存储配置文件配置的白名单,以及做进一步的处理
// 自动装配该service
public class StarterService {
// 把读取到的白名单存储到userStr中
private String userStr;
public StarterService(String userStr) {
this.userStr = userStr;
}
public String[] split(String separate){
return this.userStr.split(separate);
}
}
5、创建DoJoinPoint类
@Aspect
public class DoJoinPoint {
private Logger logger = LoggerFactory.getLogger(DoJoinPoint.class);
@Autowired
private StarterService starterService;
@Pointcut("@annotation(org.itpluto.demo.annotation.DoList)")
public void aopPoint() {
}
@Around("aopPoint()")
public Object doRouter(ProceedingJoinPoint jp) throws Throwable {
//获取内容
Method method = getMethod(jp);
DoList door = method.getAnnotation(DoList.class);
//获取字段值
String keyValue = getFiledValue(door.key(), jp.getArgs());
logger.info("itpluto list handler method:{} value:{}", method.getName(), keyValue);
if (null == keyValue || "".equals(keyValue)) return jp.proceed();
//配置内容
String[] split = starterService.split(",");
//白名单过滤
for (String str : split) {
if (keyValue.equals(str)) {
return jp.proceed();
}
}
//拦截
return returnObject(door, method);
}
private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
Signature sig = jp.getSignature();
MethodSignature methodSignature = (MethodSignature) sig;
return getClass(jp).getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
}
private Class<? extends Object> getClass(JoinPoint jp) throws NoSuchMethodException {
return jp.getTarget().getClass();
}
//返回对象
private Object returnObject(DoList doGate, Method method) throws IllegalAccessException, InstantiationException {
Class<?> returnType = method.getReturnType();
String returnJson = doGate.returnJson();
if ("".equals(returnJson)) {
return returnType.newInstance();
}
// 注意因为这里底层用的是构造器来转换的,
// 所以需要把code和info也加到构造器中,否则转换不成功
return JSON.parseObject(returnJson, returnType);
}
//获取属性值
private String getFiledValue(String filed, Object[] args) {
String filedValue = null;
for (Object arg : args) {
try {
if (null == filedValue || "".equals(filedValue)) {
filedValue = BeanUtils.getProperty(arg, filed);
} else {
break;
}
} catch (Exception e) {
if (args.length == 1) {
return args[0].toString();
}
}
}
return filedValue;
}
}
上面中间件就到此为止了,下面演示一下他的用法.
创建一个使用案例:itpluto-demo-springboot-helloworld
项目结构
依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.itpluto.demo</groupId>
<artifactId>itpluto-demo-springboot-helloworld</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- 刚才的中间件小demo导入 -->
<dependency>
<groupId>org.itpluto.demo</groupId>
<artifactId>list-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
配置文件
server:
port: 8080
# 自定义中间件配置
itpluto:
door:
enabled: true
userStr: 1001,aaaa,ccc #白名单用户ID,多个逗号隔开
实体类
@Data
public class UserInfo {
private String code;
private String info;
private String name;
private Integer age;
private String address;
public UserInfo(String code,String info,String name, Integer age, String address) {
this.code = code;
this.info = info;
this.name = name;
this.age = age;
this.address = address;
}
}
Controller类
@RestController
@RequestMapping("/base")
public class HelloWorldController {
@DoList(key = "userId", returnJson = "{\"code\":\"403\",\"info\":\"不在白名单内,没有权限!\"}")
@GetMapping("/api/queryUserInfo")
public UserInfo queryUserInfo(@RequestParam String userId) {
return new UserInfo("200","success","火锅:" + userId, 1, "上海市宝山区宝安公路");
}
}
启功类
@SpringBootApplication
public class HelloWorldApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(HelloWorldApplication.class, args);
// 测试一下启动的时候,中间件中的doJoinPoint是否注入到Spring容器
Object doJoinPoint = run.getBean("doJoinPoint");
System.out.println("doJoinPoint = " + doJoinPoint);
}
}
测试结果
1、注入成功!
2、白名单
3、黑名单
注意:在引用中间件的时候,因为只是在我们的本地,所以需要先install到我们的本地仓库,不然会找不到这个包,每次修改中间件的内容记得要clean install,这样使用方使用的才是最新的.
这是我从一个大佬的博客里面学习的小demo,他的demo不是全的,我自己按照他的demo写了一遍.有不妥的地方请指教.大佬博客地址