最近学习时编写demo代码出现异常,发现代码中的一个坑,再次记录一下,先贴代码:
public class ComponentScanner {
public static void main(String[] args) throws Exception {
}
static{
try {
System.out.println("ComponentScanner.enclosing_method(1)");
loadComponent();
System.out.println("ComponentScanner.enclosing_method(2)");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 項目中的所有class文件都加載到集合中
* @return
* @throws Exception
*/
private static List<Class> scan() throws Exception{
List<Class> result = new ArrayList<>();
List<String> list = FileScanner.scan();
int count = 0;
for (String str : list)
{
str = str.replace("\\", ".");
str = str.substring(0, str.length() - ".class".length());
// System.out.println(str);
Class c = Class.forName(str);
result.add(c);
count+=1;
}
System.out.println("scan执行:"+count);
return result;
}
/**
* 扫描项目中的所有类
* @return 类上带有@component注解的类的class对象的集合
* @throws Exception
*/
public static List<Class> loadComponent() throws Exception
{
System.out.println("ComponentScanner.loadComponent()");
List<Class> result = new ArrayList<>();
List<Class> source = scan();
for (Class c : source)
{
Component a = (Component) c.getAnnotation(Component.class);
if (a != null)
{
result.add(c);
}
}
return result;
}
}
第二段代码:
public class ObjectFactoryAnno {
public static void main(String[] args) throws Exception {
}
private static Map<Class, Object> objects = new HashMap<Class, Object>();
static{
try {
System.out.println("ObjectFactoryAnno.enclosing_method(1)");
ComponentScanner.loadComponent();
System.out.println("ObjectFactoryAnno.enclosing_method(2)");
ioc();
} catch (Exception e) {
throw new ExceptionInInitializerError("初始化异常");
}
}
/**
* 将项目中所有带有@Component注解的类 创建对象,放到objects中
* @throws Exception
*/
private static void ioc() throws Exception{
System.out.println("ObjectFactoryAnno.ioc()");
List<Class> components = ComponentScanner.loadComponent();
for (Class c : components)
{
Object obj = c.newInstance();
objects.put(c, obj);
}
}
}
其中两个类都有一个空的main函数,执行ObjectFactoryAnno,输出结果:
而执行ComponentScanner,再来看看程序运行的结果:
为什么会出现这个结果呢?解释前顺便提一下导致类加载的方式
1. 创建对象
2. 调用类中的静态方法或者 静态属性
3. 执行main 方法
4. Class.forName("类的包名.类名")
每个类会且仅会加载一次,而对象所在的类被加载会使 1. 静态代码块会被执行 2. 静态属性会被初始化
回到代码我们知道当执行ComponentScanner的loadComponent()时,调用了scan()方法,依次执行了ComponentScanner.loadComponent()—>ComponentScanner.scan()—>FileScanner.scan(),list中存放模块中所有被加载到的类的class文件,在foreach循环遍历中执行Class.forname ("ObjectFactoryAnno.class")时,ObjectFactoryAnno类被classLoader加载(与此同时,list中的部分类已经被迭代到,一部分没有进行迭代),然后在ObjectFactoryAnno的static代码块中依次执行了ComponentScanner.loadComponent()—>ComponentScanner.scan()—>FileScanner.scan(),此时继续进行和之前一样的迭代操作,list被重新赋值
由于list是static属性,并且所有操作在static块中执行因此没有被回收,和之前迭代了一半的情况冲突,于是抛出java.util.ConcurrentModificationException异常,关于这个异常的报错机制,有一篇写的很详细的文章这里给出链接: java.util.ConcurrentModificationException详解 - 简书。
另一方面,执行ObjectFactoryAnno的static块时,ClassLoader会先加载ObjectFactoryAnno类static块中的内容,当执行ComponentScanner.loadComponent()时,加载ComponentScanner类,再执行ComponentScanner的static块中的内容,此时再去调用scan方法,遍历 list时是第一次使用foreach进行迭代,Class.forName()也不会再去重复加载之前加载过的类,所以没有报错。