这篇博文我们主要介绍,循环注入和单例问题的解决。对于我们不可能去更改的某些类,如果他们之间也存在循环注入的关系,我们也不可能去更改源代码,这时,我们只能去发现其依赖关系,并抛出,交由用户去解决。
首先,准备一个循环注入的例子:
public class A {
private B b;
public A() {
}
public void setB(B b) {
this.b = b;
}
@Override
public String toString() {
return "(A类中有成员b) ";
}
}
public class B {
private C c;
public B() {
}
public void setC(C c) {
this.c = c;
}
@Override
public String toString() {
return "(B类中有成员c) ";
}
}
}
public class C {
private A a;
public C() {
}
public void setA(A a) {
this.a = a;
}
@Override
public String toString() {
return "(C类中有成员a) " + a;
}
}
上述的例子可以看到,A类中存在B类型的成员,B类型中存在C类型的成员,C类型中存在A类型的成员。由此,形成了一个循环链。这时,我们在注入的时候,就会发生循环注入。
对于上述问题的解决,我们只需要增加一个判断条件,是否这个类完成过注入!
在上一篇博客【JavaEE】自主实现Spring核心IOC和AOP之实现类的注入中,我们已经明确,要先进行Component注解的类的扫描工作,并把它加入到map中,在getBean()的时候,再进行类中的成员和方法的初始化。
所以,我们要将这个判断条件加入到getBean()方法中(也就是在getBean()方法中进行判断是否已经完成过初始化)。
针对上述的例子来看,我们首先要对A、B、C类加上Component注解,然后对每个类各自的set方法加上Autowired注解。在执行scanBeanByPackage()方法的时候,已经对A、B、C类进行了初始化,但是,类中的成员还没有进行初始化。由于这三个类是循环注入关系,所以,我们只需在getBean()的时候对他们的成员进行set()就足够了。因为这里的set操作会进行循环,所以我们需要一个判断条件。在BeanDefinition类中增加一个private boolean inject成员,来检测是否这个类的成员和方法进行过注入。看代码:
public class BeanDefinition {
private Class<?> klass;
private Object object;
private boolean inject;
private boolean singleton;
public BeanDefinition() {
this.singleton = true;
this.inject = false;
}
BeanFactory类:
@SuppressWarnings("unchecked")
public static <T> T getBean(String klassName) {
BeanDefinition beanDefinition = beanPool.get(klassName);
if (beanDefinition == null) {
System.out.println(klassName + "没有找到");
return null;
}
Object object = beanDefinition.getObject();
if (!beanDefinition.isSingleton()) {
Class<?> klass = beanDefinition.getKlass();
try {
Object obj =klass.newInstance();
beanDefinition.setObject(obj);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
if (!beanDefinition.isInject() || !beanDefinition.isSingleton()) {
beanDefinition.setInject(true);
inject(beanDefinition);
}
return (T) object;
}
private static void inject(BeanDefinition beanDefinition) {
injectField(beanDefinition);
injectMethod(beanDefinition);
}
private static void injectField(BeanDefinition beanDefinition) {
Class<?> klass = beanDefinition.getKlass();
Field[] fields = klass.getDeclaredFields();
for (Field field : fields) {
if (!field.isAnnotationPresent(Autowired.class)) {
continue ;
}
Object value = getBean(field.getType());
field.setAccessible(true);
try {
field.set(beanDefinition.getObject(), value);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
private static void injectMethod(BeanDefinition beanDefinition) {
Class<?> klass = beanDefinition.getKlass();
Method[] methods = klass.getDeclaredMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(Autowired.class) || method.getParameterCount() != 1
|| !method.getName().startsWith("set")) {
continue ;
}
Parameter[] parameters = method.getParameters();
Class<?> paraKlass = parameters[0].getType();
Object obj = getBean(paraKlass);
try {
method.invoke(beanDefinition.getObject(),obj);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
上述代码可见,我们默认的inject是false,我们在getBean方法里面先检测inject是否为false,如果为false,那么证明这个类的成员和方法还没有注入过,那么就需要进行注入。我们通过inject()方法进行注入,最值得注意的是,我们在注入之前,先设置inject为true,这就是避免循环注入的基础。因为当注入C类的成员的时候,会需要A类型的BeanDefinition,我们检测A的inject为true,条件不满足,所以,循环会停止,注入也就会很正确的完成。
对于单例,大家已经发现,上述所给的BeanDefinition类里多了一个singleton成员,这个成员默认为true,我们在getBean()方法中取得这个成员的时候,检测是否为单例,如果不是,我们需要新生成一个对象,这个对象会覆盖原来的object,由此,我们新得到了一个对象;若是单例,我们就继续取得这个原对象,这就是单例的。由此,我们解决了单例和非单例问题。
其实对于发现循环依赖关系这个问题,我们在博客【JavaEE】自主实现Spring核心IOC和AOP之实现类的注入中已经实现的差不多了。因为我们当时采取的操作是:
1.包扫描遍历所有的类,将含有Component注解的类加入到beanMap中,同时生成该类对应的BeanDefinition。
2.在每一次扫描完Component注解后,仍在该类进行Bean注解的扫描,一旦有Bean注解了,先从beanPool里面获取该方法的参数值,如果全能得到,将该方法形成MethodDefinition加入到okMethod列表里;如果得不到,就放入到unOkMethod列表里,并且,将以未取得参数值的类型为键,建立一个List且类型为MethodDefinition,将方法形成的MethodDefinition加入进去,然后继续进行遍历。
3.当所有的遍历都完毕后,开始执行okMethod列表里的方法,执行完以后,将返回值类型,再次形成一个键值对,放到beanPool里面。然后循环操作,直到okMethod列表空为止。这时,如果unOkMethod列表中还有为解决的类,那么这些类,有两种可能性,一种是压根就没有,第二种就是存在循环依赖关系,对于这种存在依赖关系的类,我们应该交给用户来处理。
所以,我们应该在getBean()方法中加入一个输出(或抛出一个异常)。
private void showCircleDependency() {
System.out.println(MethodDependence.getUndependence());
}
@SuppressWarnings("unchecked")
public <T> T getBean(String className) {
BeanDefinition beanDefinition = beanPool.get(klassName);
if (beanDefinition == null) {
showCircleDependency();
System.out.println("Bean[" + className + "]不存在!");
return null;
}
Object object = beanDefinition.getObject();
if (!beanDefinition.isSingleton()) {
Class<?> klass = beanDefinition.getKlass();
try {
Object obj =klass.newInstance();
beanDefinition.setObject(obj);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
if (!beanDefinition.isInject() || !beanDefinition.isSingleton()) {
beanDefinition.setInject(true);
inject(beanDefinition);
}
return (T) object;
}
// MethodDependence类应增加一个方法!
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();
}
接下来,我们来做一个测试:
// 对例子中的类加入Component注解,对类中的成员加上Autowired注解
public static void main(String[] args) {
BeanFactory.scanPackage("com.mec.mpring.demo");
BeanFactory beanFactory = new BeanFactory();
A a = beanFactory.getBean(A.class);
B b = beanFactory.getBean(B.class);
C c = beanFactory.getBean(C.class);
System.out.println("a:" + a);
System.out.println("b : " + b);
System.out.println("c:" + c);
]
结果:
@Component
public class One {
public One() {
}
@Bean
public Two getTwo(Three three) {
return new Two();
}
@Bean
public Three getThree(Two two) {
return new Three();
}
}
public class Two {
public Two() {
}
@Override
public String toString() {
return "这是Two类 ";
}
}
public class Three {
public Three() {
}
@Override
public String toString() {
return "这是Three类";
}
}
public static void main(String[] args) {
BeanFactory.scanPackage("com.mec.mpring.demo");
BeanFactory beanFactory = new BeanFactory();
Two two = beanFactory.getBean(Two.class);
System.out.println(two);
System.out.println("----------------------------");
Three three = beanFactory.getBean(Three.class);
System.out.println(three);
}
结果如下:
由结果可知,模拟Spring核心IOC之实现循环注入、单例、发现循环依赖正确。