从ClassLoader双亲委派看工厂模式

本文探讨了双亲委派模式在类加载过程中的作用,以及它可能导致的安全问题。通过分析简单工厂模式在类加载器层级访问限制下的局限性,引入抽象工厂模式作为解决方案,展示了如何在不同类加载器下正确实现类的实例化,从而避免了双亲委派模式带来的问题。

目录

双亲委派模式问题

简单工厂模式

抽象工厂模式

示例


双亲委派模式问题

      双亲委派模式在类加载时会先交由其双亲加载器来处理,如果请求失败了,再由自己加载,这样做其中一点是出于安全考虑。检查类有没有被加载前面的日志里总结过,是从最底层自定义类加载器开始往上检查(加载类则是从顶到往下加载),最后到启动类加载器,这里有一点要注意的是,底层的类可以访问其双亲类加载器加载的类,反过来就不行,顶层的类无法访问自己下层加载的类,这样就会出现一些问题,我们知道启动类加载器负责加载系统中一些核心类,例如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个组成部分:

  1. 抽象工厂:工厂类的接口,下面的子类需要用到工厂模式时都先要实现这个接口。
  2. 具体工厂类:子类中实现抽象工厂后,提供创建实例方法里,根据传入条件选择需要导出的实例类型。
  3. 抽象导出:创建实例的导出方法需要实现的接口。
  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加载的类,简单工厂模式中我们把工厂核心类和工厂方法都放到一起,由最顶层的启动类加载器加载,当下层有子类需要实现工厂类时,由于工厂核心类无法访问,所以工厂方法无法创建子类的实例,而在抽象工厂模式中,因为工厂方法分离出来,交由子类去实现,导出什么类型的实例,如何导出等这些业务逻辑都是子类去实现,所以工厂类无需亲自决定创建何种类型的子类,也就不会因无法访问而导致无法创建实例的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值