在上一篇文章中《模拟Spring IoC(二)Bean注解》解释了Bean注解的应用场景,但是没有处理有参方法的Bean注解,这边文章将主要描述Bean注解含参方法的处理过程。
分析:
含参方法处理的关键是:方法所依赖的参数是否满足?
如果满足,则,该方法是可以执行并得到一个Bean的。
如果最终都无法满足,那么必然形成了循环依赖,无法获取参数,则将循环依赖告知用户。
核心是:检测依赖是否满足。
这里可以先考虑构造MethodDefinition
MethodDefinition
封装了方法反射执行所需要的对象:
1、对象;
2、方法本身;
3、参数对象集合。
这里最难解决的问题是:参数所需要的对象可能是随机满足的,即当你想要执行该方法时,该方法所需要的某个参数此时可能并没有存储在beanPool中。
处理方法:
将所有不满足依赖关系的方法中的参数形成一个Map(比如dependenceMethodPool ),此Map的键是参数类型,而值是MethodDefinition所形成的List(即含有该参数类型的所有方法组成的列表),此map用于管理参数类型与需要该参数类型的方法的映射关系。再准备一个参数不满足(暂时无法执行)的方法列表(列表1),参数满足要求的(即可执行的)方法列表(列表2)。
过程:
1、遇到一个带参方法,先检测其所有参数;
1.1、若参数满足要求,则,暂不处理(可以将count–),这里的count是“参数个数”
1.2、若参数不能得到满足,则,将这个参数类型作为键,这个MethodDefinition作为值,存储到Map中;
1.3、当对所有参数都进行了检测,若存在未满足的参数,则,将MethodDefinition存储到列表1;
1.4、当所有参数都满足要求,则,将其存储到列表2;
2、每处理完一个Bean,都扫描Map,将依赖这个Bean的MethodDefinition的count–;若count为0了,说明参数都满足了,则,将其存储到列表2中。
实现
MethodDefinition:
public class MethodDefinition {
private Method method;//方法本身
private Object object;//执行方法的对象
private int paraCount;//方法的参数个数
public MethodDefinition() {
}
Method getMethod() {
return method;
}
void setMethod(Method method) {
this.method = method;
}
Object getObject() {
return object;
}
void setObject(Object object) {
this.object = object;
}
void setParaCount(int paraCount) {
this.paraCount = paraCount;
}
//参数个数增加
void add() {
++this.paraCount;
}
//参数个数减少
int sub() {
return --this.paraCount;
}
这是处理带参方法的入口,该段程序属于dealBean方法:
/*
*
* 获取beanPool中没有的参数
* 1.获取方法的参数类型,对参数类型进行去重操作再装入paraPool
* 2.产生一个ArrayList,在BeanPool中查找是否存在该参数类型,
* 3.若存在,则从paraPool中删掉
* */
private static Map<Class<?>,Integer> getMethodPara(Object object,Method method) {
Map<Class<?>, Integer> paraPool = new HashMap<>();//存储BeanPool中没有的参数
Class<?>[] parameterTypes = method.getParameterTypes();
for(int index = 0;index <parameterTypes.length;index++) {
//HashMap中不存在相同的key值,put方法会自动调用equals方法和hashCode
//去重;去掉类型重复的参数类型
paraPool.put(parameterTypes[index], 0);//暂时只需要键,即参数类型,至于缺失参数的方法目前不put
}
//Map和List不能边遍历边删除,先将其存储到klassList
List<Class<?>> klassList = new ArrayList<Class<?>>();
for(Class<?> type:paraPool.keySet()) {
//此处只需要判断bean是否存在,使用get()
BeanDefinition beanDefinition = BeanPool.get(type.getName());
if(beanDefinition != null) {
klassList.add(type);
}
}
for(Class<?>type:klassList) {
paraPool.remove(type);
}
//避免边遍历边删除
return paraPool;
}
注意:之所以不把参数存放在List列表而存放在Map中,是因为每次将参数放入List,都需要检查一遍前面有没有放入过该参数,如此一来时间复杂度会很高。而用Map来存放的话,是通过键值对的方式,一个键只能对应一个值,当有相同的键加入时Map时,会自动覆盖前面这个键对应的值,不需要再花费时间复杂度去检查有没有,也不存在键的重复,这个过程中,键所对应的的值并不重要,因为我们需要的只是不重复的参数类型。就算某个方法的参数有三个,其中两个参数是同一个类型的,虽然我们把参数在Map中加入了三次,但是实际上Map中存放的只有两种参数类型,这样避免了对相同参数类型的参数重复加入。
处理带参数的方法:先判断方法的所有参数是否满足(getMethodPara方法),为空则执行方法,不为空则将方法加入参数不满足列表
private static void dealMethodWithPara(Class<?> klass,Object object,Method method) {
Map<Class<?>, Integer> paraTypeMap = getMethodPara(object, method);
//若为空,即说明所有参数均以获取,可执行方法
if(paraTypeMap.isEmpty()) {
invokeMethodWithPara(klass,object, method);
return;
}
//若不为空,说明该方法执行需要的参数不满足
//将该方法的执行对象,方法本身和参数个数进行封装
//加入到未执行方法列表。
MethodDefinition methodDefinition = new MethodDefinition();
methodDefinition.setObject(object);
methodDefinition.setMethod(method);
methodDefinition.setParaCount(paraTypeMap.size());
MethodDependence.addUninvokeMethod(methodDefinition, paraTypeMap);
}
当参数满足后,执行带参数的方法
static void invokeMethodWithPara(Class<?> klass,Object object,Method method) {
Class<?>[] paraTypes = method.getParameterTypes();//获取方法所有参数的类型
Object[] paraValues = new Object[paraTypes.length];//生成对应参数个数的实参列表
for(int index=0;index<paraTypes.length;index++) {
Class<?> paraklass = paraTypes[index];
//这里的getBeanObject()是一定可以取到的,
//在该方法外层已经判断过,所有需要的参数均存储在BeanPool
BeanDefinition beanDefinition = getBeanObject(paraklass.getName());
paraValues[index] = beanDefinition.getObject();
}
try {
//执行该方法,把该方法执行之后产生的bean也才存储在BeanPool中
Class<?> beanClass = method.getReturnType();
Object bean = method.invoke(object, paraValues);
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setKlass(beanClass);
beanDefinition.setObject(bean);
//注入和单例保持默认
BeanPool.put(beanClass.getName(), beanDefinition);
MethodDependence.checkDependence(beanClass);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
类----MethodDependence,包含四个方法,分别是将方法加入参数不满足列表、检查依赖(即检查方法需要的参数是否满足)、执行参数满足列表中的方法、输出dependenceMethodPool中剩余的内容(即最终也无法满足依赖的参数和对应的方法)
public class MethodDependence {
private static final List<MethodDefinition> uninvokeMethodList
= new ArrayList<MethodDefinition>();//存储参数不满足的方法
private static final List<MethodDefinition> invokeableMethodList
= new LinkedList<MethodDefinition>();//存储满足参数,可执行的方法
private static final Map<Class<?>, List<MethodDefinition>>
dependenceMethodPool = new HashMap<Class<?>, List<MethodDefinition>>();//存储暂时不存在的参数类型
MethodDependence() {
}
/*
* 当遇到参数不满足的方法时:
* 当发现方法的参数不满足时,需要把方法放到uninvokeMethodList,
* 并将不满足的参数类型放到dependenceMethodPool
* 如果pool里不存在该参数类型,创建一个以该类型为key,value暂时为空的键值对
* 存在,根据该参数类型获取对应的方法列表,把该方法加入列表中
*/
static void addUninvokeMethod(MethodDefinition methodDefinition,
Map<Class<?>, Integer> paraTypePool) {
uninvokeMethodList.add(methodDefinition);
for (Class<?> paraType : paraTypePool.keySet()) {
//判断参数类型是否在Pool中,
//如不包含,增加一个键值对
if (!dependenceMethodPool.containsKey(paraType)) {
List<MethodDefinition> methodList = new ArrayList<>();
dependenceMethodPool.put(paraType, methodList);
}
//包含则从Pool获取methodDefination,加到methodList中
List<MethodDefinition> methodList = dependenceMethodPool.get(paraType);
methodList.add(methodDefinition);
}
}
/*
* 检查依赖:
* 每次只要产生bean,即向beanPool中进行put,就要执行检查依赖操作
* 根据类型获取map中的mdList
* 遍历列表 ,判断列表中方法的参数个数是否为0
* 若为0,及说明该方法需要的所有参数均已满足,将其加入okMethodList
* (okMethodList为中间临时存储可执行方法的列表)
* 若okMethodList不为空,将该列表中方法从未满足列表删除,加入可执行列表
*/
static void checkDependence(Class<?> beanClass) {
List<MethodDefinition> mdList = dependenceMethodPool.get(beanClass);
if (mdList == null) {
return;
}
List<MethodDefinition> okMethodList = new ArrayList<>();
for (MethodDefinition md : mdList) {
//此处调用sub(),是因为之前的操作确定了beanPool中
//新增了一个之前不存在的参数类型,现在找到了该参数类型,
//那么该参数类型对应的方法的参数个数就需要减1
if (md.sub() == 0) {
okMethodList.add(md);
}
}
//若okMethodList不为空,说明该方法可执行
//将该方法从uninvokeMethodList删除,加入invokeableMethodList
if (!okMethodList.isEmpty()) {
for (MethodDefinition method : okMethodList) {
uninvokeMethodList.remove(method);
invokeableMethodList.add(method);
}
}
dependenceMethodPool.remove(beanClass);//删除pool中的键
}
//执行invokeableMethodList列表中方法
static void invokeDependenceMethod() {
//每次执行“可执行列表”中方法,每次执行一个,删除一个方法,直到列表中没有方法
while (!invokeableMethodList.isEmpty()) {
MethodDefinition methodDefinition = invokeableMethodList.get(0);
Object object = methodDefinition.getObject();
Class<?> klass = object.getClass();
Method method = methodDefinition.getMethod();
invokeableMethodList.remove(0);//只删除第一个方法
//调用BeanFactory中的方法,执行参数满足的方法
BeanFactory.invokeMethodWithPara(klass, object, method);
}
}
//输出dependenceMethodPool中最后残留的方法和其参数类型
static String getUndependence() {
StringBuffer str = new StringBuffer();
for (Class<?> dependenceClass : dependenceMethodPool.keySet()) {
List<MethodDefinition> mdList = dependenceMethodPool.get(dependenceClass);
for (MethodDefinition md : mdList) {
str.append(md.getMethod())
.append(" --> ").append(dependenceClass.getName())
.append('\n');
}
}
return str.toString();
}
}
若将所有可装入的Bean均装载完成,还是有方法的参数不能满足,如A m1(B)
,B m2(A)
,这两个方法的参数为彼此,这就导致两个方法最终都互相牵制,无法执行,这就是***循环依赖***。对于这样的情况,只能进行输出,由用户自行处理。
//输出循环依赖,由用户判断
private void showCircleDependency() {
System.out.println(MethodDependence.getUndependence());
}
这就是对带参方法的整个处理过程。
下面进行一下测试
@Component
public class Config {
public Config() {
}
@Bean
public Two getTwo(One one) {//Two的参数类型为One
Two two = new Two();
two.setOne(one);
return two;
}
@Bean
public One getOne(Two two) {//One的参数类型为Two
One one = new One();
one.setTwo(two);
return one;
}
}
//测试
One o = beanFactory.getBean(One.class);
运行结果:输出无法执行的方法以及对应的参数类型
到目前为止,还有一个注解Qualifier,下一篇博客将会解释该注解的作用以及用法