目录
引言
曾经有人问我,假如有一个业务或者数据处理逻辑,会根据甲方客户繁杂的业务需求,而动态变化,该怎么处理,具体怎么实现?
将所有策略strategy都写出来,然后放入一个hashMap,根据不同的业务需求,调用不同的策略就行了。
再问:那如果我的策略动态多变,再将strategy放入Map的代码,不想写死,不想频繁的修改代码怎么实现?比如我将策略代码写入项目下某个文件夹下面某个文件里,或者直接存入数据库某张表里,策略代码用text类型字段存起来,再调用构建接口,将策略逻辑用类加载器,加载出来,动态的创建为一个个新的strategy对象呢?
读取文件或者text字段,并解析为java代码,编译后,用类加载的方式加载,并且注册进Map,这个稍微复杂,但是我可以实现,只用在代码中添加策略,但是不用修改注册逻辑代码的方式。
设计思路:
-
所有需要被自动添加的策略类 Strategy 都必须加注解
-
再做一个扫描注解,属性包含:标明需要被扫描的包路径,@Import 标注一个注册器(实现ImportBeanDefinitionRegistrar),
-
定义一个注册器,实现ImportBeanDefinitionRegistrar,该注册器可以根据上述注解找到所有被注解标注的类,然后获取到标注的名称,实例化该类,并注册入指定的缓存。
将此注解加注在springboot启动类上面就可以在项目启动的时候,按照指定的包路径去扫描指定注解的类,然后利用反射机制,获取到扫描到的(被标注注解的)类的元空间,有了元空间,不管是获取该类上的注解,注解属性,还是该类的属性,方法都是没问题的。那么就可以获取到被扫描到的策略strategy的名字,再利用反射 clazz.newInstance() 创建出实例,放入缓存,就解决了动态注册策略的目的了。
除了对反射的利用,还需要对spring 容器有一定的理解,才能利用spring容器的environment, resourceLoader ,做出扫描器,使用resourceLoader 扫描该环境下,所有带注解的类;再根据指定的注解,过滤出目标类。
代码实现:
标注注解:@StrategyClass
import io.swagger.v3.oas.annotations.tags.Tag;
import java.lang.annotation.*;
/**
* @Title: StrategyClass
* @Description: 用来标记需要被注册的strategy
* @Author: wenrong
* @Date: 2024/4/2 10:02
* @Version:1.0
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface StrategyClass {
/**
* 策略名
* 为空时,会尝试读取 {@link Tag#name()} 属性
*/
String strategyName() default "";
}
扫描注解:trategyScan
@DynamicStrategyScan注解标记在springboot启动类上,在springboot启动的时候,就会扫描@StrategyClass 注解的策略类,并开始自动添加到Map 缓存。
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* @Title: DynamicStrategyScan
* @Description: 用来扫描有动态策略注解的类,并初始化成相应的配置,注册到JVM 缓存
* @Author: wenrong
* @Date: 2024/4/2 15:27
* @Version:1.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(DynamicStrategyRegister.class)
@Documented
public @interface DynamicStrategyScan {
String[] value() default {};
String[] basePackages() default {};
}
注册器抽象类:
因为可能不光策略需要被注册,其他的也可能需要被注册,所以这里这里做一下抽象:
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @Title: AnnotationScanAndRegister
* @Description:
* @Author: wenrong
* @Date: 2024/4/2 15:35
* @Version:1.0
*/
public abstract class AnnotationScanAndRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
/**
* 资源加载器
*/
protected ResourceLoader resourceLoader;
/**
* 环境
*/
protected Environment environment;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
ImportBeanDefinitionRegistrar.super.registerBeanDefinitions(importingClassMetadata, registry);
}
/**
* author: wenrong
* date: 2024/4/2 15:36
* 获取base packages
*/
protected static Set<String> getBasePackages(AnnotationMetadata importingClassMetadata, Class<? extends Annotation>