目录
双亲委派模式问题
双亲委派模式在类加载时会先交由其双亲加载器来处理,如果请求失败了,再由自己加载,这样做其中一点是出于安全考虑。检查类有没有被加载前面的日志里总结过,是从最底层自定义类加载器开始往上检查(加载类则是从顶到往下加载),最后到启动类加载器,这里有一点要注意的是,底层的类可以访问其双亲类加载器加载的类,反过来就不行,顶层的类无法访问自己下层加载的类,这样就会出现一些问题,我们知道启动类加载器负责加载系统中一些核心类,例如String类,还有一些类的接口加载,假设在系统类中有一个简单工厂模式接口以及工厂方法,在下层的应用类加载器加载的类中,有类实现了这个工厂模式,下层应用类可以访问其双亲加载器加载的类,这没问题,但是由于双亲类加载器或者说上层ClassLoader无法访问下层类加载器加载的类,导致工厂方法中不能创建生成应用类实例的方法,这种情况会出现在简单工厂模式中。
简单工厂模式
简单工厂模式,简单来说,就是只有一个工厂方法,在该方法中负责所有类型的实例创建,例如一个animal工厂,创建动物实例,如果我们有两三种动物,它们的实例创建全部放在了一个工厂方法里,实例种类数量不多时没什么问题,当实例种类很多事,大量重复的if else代码放在一起,工厂方法变得臃肿,这还不是主要的问题,上面的ClassLoader双亲委派模式中,如果工厂类接口,工厂方法和具体实现的应用类不在同一类加载器,那么就会出现工厂无法创建生成实例的方法。
public interface Animal {
void create();
}
public class AnimalFactory {
public Animal getAnimal(String animalType) {
if (animalType == null) {
return null;
}
if (animalType.equalsIgnoreCase("Cat")) {
return new Cat();
} else if (animalType.equalsIgnoreCase("Puppy")) {
return new Puppy();
} else if (animalType.equalsIgnoreCase("Lion")) {
return new Lion();
}
// ....
return null;
}
}
如上图的场景,有一个接口Animal和工厂方法,假设这些都交由启动类加载器加载,然后在Java程序中某些类需要实现这个工厂接口,用到其来创建实例:
public class Cat implements Animal {
@Override
public void create() {
System.out.println("Cat.");
}
}
public class Puppy implements Animal {
@Override
public void create() {
System.out.println("Puppy.");
}
}
public class Lion implements Animal {
@Override
public void create() {
System.out.println("Lion.");
}
}
现在有三个具体动物类Cat,Puppy和Lion,它们都实现Animal工厂接口,希望通过工厂方法创建实例,假设这三个具体类都是由应用类加载器完成加载,那么工厂方法就无法创建这些类的实例,原因是工厂类作为最顶层启动类加载器加载的核心类,无法访问下层ClassLoader加载的类。
抽象工厂模式
若想解决上面的问题,可以选择把简单工厂模式换成抽象工厂模式,抽象工厂模式简单来说,就是工厂具体角色的创建(例如上面的例子中,抽象角色是animal,具体角色是cat,puppy)方法不再是由工厂方法来实现,而是交由具体角色自己的类来实现,这样的抽象工厂模式中,一共分成4个组成部分:
- 抽象工厂:工厂类的接口,下面的子类需要用到工厂模式时都先要实现这个接口。
- 具体工厂类:子类中实现抽象工厂后,提供创建实例方法里,根据传入条件选择需要导出的实例类型。
- 抽象导出:创建实例的导出方法需要实现的接口。
- 具体导出类:根据具体工程类中创建实例方法调用对于的导出方法后,实现导出方法里的业务逻辑。
这部分我表述不太好的,来直接看一个例子,总结在抽象工厂中每一部分各自做的事情。
示例
还是上面的Animal示例,首先是一个抽象工厂接口,它只提供一个输出类实例的方法,也就是抽象导出接口,注意,这个方法也是一个需要实现的接口,因为上面说了,具体的类实例创建逻辑不再由抽象工厂提供,而是子类自己实现:
// 抽象工厂
public interface CreateFactory {
public CreationExport factory(String type);
}
// 抽象导出
public interface CreationExport {
public boolean export();
}
子类实现了抽象工厂接口后,自己实现工厂方法来决定如何导出实例:
// 具体工厂类
public class AnimalFactory implements CreateFactory {
@Override
public CreationExport factory(String type) {
if(type.equals("cat")) {
return new ExportCat();
} else if (type.equals("lion")) {
return new ExportPuppy();
} else {
throw new RuntimeException("type not found.");
}
}
}
可以看到,原来在简单工厂模式中,由工厂类提供的逻辑实现分离出来,交由Animal子类实现,决定导出什么类型的实例后,最后实现的就是具体导出方法:
// 具体导出类
public class ExportCat implements CreationExport {
@Override
public boolean export() {
System.out.println("Cat");
return true;
}
}
public class ExportLion implements CreationExport {
@Override
public boolean export() {
System.out.println("Lion");
return true;
}
}
具体导出很简单,就是负责一些创建前的逻辑处理,最后导出具体类的实例对象。
到这里相信大家都看出来了,抽象工厂是如何解决上面说的双亲委派带来的问题,也就是顶层类加载器加载的类无法访问下层ClassLader加载的类,简单工厂模式中我们把工厂核心类和工厂方法都放到一起,由最顶层的启动类加载器加载,当下层有子类需要实现工厂类时,由于工厂核心类无法访问,所以工厂方法无法创建子类的实例,而在抽象工厂模式中,因为工厂方法分离出来,交由子类去实现,导出什么类型的实例,如何导出等这些业务逻辑都是子类去实现,所以工厂类无需亲自决定创建何种类型的子类,也就不会因无法访问而导致无法创建实例的问题。
本文探讨了双亲委派模式在类加载过程中的作用,以及它可能导致的安全问题。通过分析简单工厂模式在类加载器层级访问限制下的局限性,引入抽象工厂模式作为解决方案,展示了如何在不同类加载器下正确实现类的实例化,从而避免了双亲委派模式带来的问题。
1万+

被折叠的 条评论
为什么被折叠?



