前言
需求:
用key找到对应的方法实现。使用注解的形式增量开发。
@MyComponent
public class Sample1 {
@MyMethod(key = "key1")
public String test2() {
return "Shenzhen";
}
}
任意时刻都能通过key来进行依赖查找
@Test
public void test() {
Assert.notNull(myBeanFactory.getMethod("key1"), "key1对应的方法不能为空");
}
实现思路:
- 声明自己的类注解,并要求被 Spring 收集
- 声明自己的方法注解,确保可以通过反射获取
- 借 Spring 的能力,容器启动收集bean完成后,把bean列表交给自己,用于自己的收集策略。
1. 声明注解
- 类注解
@Target({
ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 继承Spring的注解,确保类能被Spring扫描
@Component
public @interface MyComponent {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
- 方法注解
@Target({
ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyMethod {
// 用于依赖查找的key
String key();
}
2. 使用 Spring 的工厂拓展
找到我们用注解标记的类、方法,Spring 收集完bean之后提供了拓展点供我们遍历这些bean。
- BeanFactoryPostProcessor 接口
- 其中 ConfigurableListableBeanFactory beanFactory 提供了容器
@Component
public class MyBeanFactory implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 找到所有注解标记的bean
Map<String, Object> beanMap = beanFactory.getBeansWithAnnotation(MyComponent.class);
beanMap.forEach((beanName, bean) -> {
// 收集bean中用注解标记的方法 (方法先省略)
// collectMethod(bean);
});
}
}
3. 收集策略
- 收集策略可以很简单,这里就用一个Map收集
- 用Spring的AnnotationUtils.findAnnotation(method, MyMethod.class) 命中标识的方法
- 如果命中,则拿到注解中的key,收集key和Method的映射关系
// 自己的容器
private static final Map<String, Method> METHOD_MAP = new HashMap<>();
public Method getMethod(String key) {
return METHOD_MAP.get(key);
}
private void collectMethod(Object bean) {
Method[] methods = bean.getClass().getDeclaredMethods();
Arrays.stream(methods)
// 过滤: 只要MyMethod标识的方法
.filter(method -> Objects.nonNull(getAnnotation(method)))
// 收集: 通过MyMethod的注解key,绑定依赖关系,放到自己的容器
.forEach(method -> METHOD_MAP.putIfAbsent(getAnnotation(method).key(), method));
}
private static MyMethod getAnnotation(Method method) {
return AnnotationUtils.findAnnotation(method, MyMethod.class);
}
4. 完整的代码
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class MyBeanFactory implements BeanFactoryPostProcessor {
private static final ConcurrentHashMap<String, Method> METHOD_MAP = new ConcurrentHashMap<>();
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 从Spring容器中获取所有MyController
Map<String, Object> beanMap = beanFactory.getBeansWithAnnotation(MyComponent.class);
beanMap.forEach((beanName, bean) -> {
// 收集bean里面的所有可用方法
collectMethod(bean);
});
}
private void collectMethod(Object bean) {
Method[] methods = bean.getClass().getDeclaredMethods();
Arrays.stream(methods)
// 过滤: 只要MyMethod标识的方法
.filter(method -> Objects.nonNull(getAnnotation(method)))
// 收集: 通过MyMethod的注解key,绑定依赖关系,放到自己的容器
.forEach(method -> METHOD_MAP.putIfAbsent(getAnnotation(method).key(), method));
}
private static MyMethod getAnnotation(Method method) {
return AnnotationUtils.findAnnotation(method, MyMethod.class);
}
public Method getMethod(String key) {
return METHOD_MAP.get(key);
}
}
后记
记录下用到的 Spring 的 api 和工具
-
一个核心的接口方法获取bean容器
BeanFactoryPostProcessor#postProcessBeanFactory
-
获取指定注解修饰的类
ConfigurableListableBeanFactory#getBeansWithAnnotation
-
获取方法上的注解内容
AnnotationUtils#findAnnotation