Spring中的订阅发布队列:
要实现类似Spring中根据消息类型转发消息到指定的订阅者上面
首先想到的就是利用接口和泛型,让订阅者实现接口,那么就只需要通过获取到接口的所有实现类和实现类中的泛型类型,就可以将消息类型和订阅者进行绑定
理论存在,实践开始!
步骤
- 定义元消息数据格式
- 定义发布者接口以及方法
- 定义订阅者接口以及方法
- 实现将消息类型和订阅者绑定
- 订阅发布队列的使用
- Spring中使用
定义元消息数据格式
/**
* 消息的元数据
*/
public class MetaData {
@Override
public String toString() {
return "MetaData{" +
"message=" + message +
'}';
}
private Object message;
public Object getMessage() {
return message;
}
public void setMessage(Object message) {
this.message = message;
}
}
定义发布者接口以及方法
/**
* 实现接口,标记为发布者
*/
public interface Publisher<T extends MetaData> {
void publish(T data);
}
定义订阅者接口以及方法
/**
* 实现此接口将类标记为订阅者
*/
public interface Subscriber<T extends MetaData> {
void onEvent(T message);
}
实现将消息类型和订阅者绑定
- 定义好Map格式,用来存储订阅者
2.找到接口的实现类,并将里面的泛型类型找出来,最后放到对应的map中
动作:扫描包下的所有类,判断是否为指定接口的实现类,并获取泛型参数
/**
* 将注册的订阅者 存放到这里
*/
public class SubscriberMap {
public static Map<Class<? extends MetaData>, List<Class<? extends Subscriber>>> subscribers = new HashMap<>();
/**
* 将指定接口的实现类根据泛型类型,注入到订阅者Map中
* @param subscriber
*/
public static void injectSubscriberToMap(Class<? extends Subscriber> subscriber){
Package[] packages = Package.getPackages();
for(Package pkg:packages){
String pkgName = pkg.getName();
List<Class<?>> classes = getClasses(pkgName);
for(Class clazz:classes){
// 判断此类是否为是接口实现类
if(subscriber.isAssignableFrom(clazz) && !clazz.isInterface() && !clazz.isEnum()){
// 获取实现类中的泛型类型
Type[] genericInterfaces = clazz.getGenericInterfaces();
for(Type genericInterface : genericInterfaces){
if(genericInterface instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericInterface).getActualTypeArguments();
for(Type actualTypeArgument : actualTypeArguments){
String typeName = actualTypeArgument.getTypeName();
try {
Class<?> aClass = Class.forName(typeName);
if(MetaData.class.isAssignableFrom(aClass)){
// 如果泛型类是MetaData子类,则注入
List<Class<? extends Subscriber>> list = subscribers.getOrDefault(aClass, new ArrayList<>());
list.add((Class<? extends Subscriber>)clazz);
subscribers.put((Class<? extends MetaData>)aClass,list);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
}
}
/**
* 获取指定包下的所有类
* @param packageName
* @return
*/
public static List<Class<?>> getClasses(String packageName) {
List<Class<?>> classes = new ArrayList<>();
String path = packageName.replace('.', '/');
try {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> resources = classLoader.getResources(path);
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
File directory = new File(resource.getFile());
if (directory.exists()) {
String[] files = directory.list();
for (String file : files) {
if (file.endsWith(".class")) {
String className = packageName + '.' + file.substring(0, file.length() - 6);
try {
Class<?> clazz = Class.forName(className);
classes.add(clazz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return classes;
}
订阅发布队列的基本使用
-
实现 发布者
public class Publisher_1 implements Publisher<Meta1>{ @Override public void publish(Meta1 data) { // RegisterMap.notifySubscribe(data.getClass(),data); InjectMap.notifySubscriber(data); } }
-
实现订阅者
public class Subscriber_1 implements Subscriber<Meta1> { @Override public void onEvent(Meta1 message) { System.out.println("receiver: " + message.toString()); } { // RegisterMap.register(Meta1.class,this); } }
-
定义消息格式
public class Meta1 extends MetaData{ }
-
测试
public static void main(String[] args) {
// 先注入订阅者队列
injectSubscriberToMap(Subscriber.class);
// 发送消息
Publisher_1 publisher1 = new Publisher_1();
Meta1 meta1 = new Meta1();
meta1.setMessage("hello world queue");
publisher1.publish(meta1);
}
Spring中使用
-
将InjectMap注入到spring中,并将初始化这一步交给spring,代码如下
@Component public class InjectMap implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { injectSubscriberToMap(Subscriber.class); } }
-
可以写一个controller或者写一个测试用例
@SpringBootTest public class T { @Test public void test(){ Publisher_1 publisher1 = new Publisher_1(); Meta1 metaData = new Meta1(); metaData.setMessage("123456"); publisher1.publish((Meta1) metaData); } }
总结:
class.forName() 和 ClassLoader的用法
不同:
classforName可以直接根据类路径加载类(对类是未知的),Loader是明确类的情况下加载的
相同:
两种方法都只是加载出类的Class对象,但并没有实例化对象
实例化对象:
Constructor constructor = clazz.getConstructor();
Subscriber o = (Subscriber) constructor.newInstance();
泛型的使用:
- 对于<? extend Obj>类型的泛型,最好是只读的,如果要使用只能强制类型转换,会导致编译器无法帮我们规避类型错误
- Class对象和类实例的区别,
Class
对象是用于表示类的结构信息的,只有一个,而类实例是表示类的具体对象的,N个。Class
对象包含了类中的方法、字段等结构信息,而类实例包含了类的属性值和对象的状态。