1. 程式人生 > >基於Spring註解實現的工廠模式

基於Spring註解實現的工廠模式

摘要: 工廠模式是大家熟知的一種設計模式,在spring BeanFactory將這模式運用自如。 前面講過如果通過xml配置的方式實現,今天我們來講講如何通過註解的方式實現工廠模式。 主要思路 1. 掃描classPath下的的類,將這些class儲存到setmap中 2. 遍歷set中的class,找出被自定義facory註解註解過的的class,以beanId,class的物件形式封裝到一個map集合裡 3. 暴露一個方法getBean,通過beanId查詢對於的class的物件,匹配成功後返回該物件

[2017-5-16 by Daniel] 今天發現了更簡單的方法,請見本人的這篇文章《

基於Spring註解實現工廠模式》。

工廠模式是大家熟知的一種設計模式,在spring BeanFactory將這模式運用自如。 前面講過如果通過xml配置的方式實現,今天我們來講講如何通過註解的方式實現工廠模式。 主要思路

  1. 掃描classPath下的的類,將這些class儲存到setmap中
  2. 遍歷set中的class,找出被自定義facory註解註解過的的class,以beanId,class的物件形式封裝到一個map集合裡
  3. 暴露一個方法getBean,通過beanId查詢對於的class的物件,匹配成功後返回該物件

同樣回顧下常見的簡單工廠寫法

建立一個介面類Pizza


public interface
Pizza
{ publicfloatgetPrice(); }

MargheritaPizza 類


public classMargheritaPizzaimplementsPizza{
    publicfloatgetPrice(){
        System.out.println("8.5f");
        return 8.5f;

    }
}

CalzonePizza 類


public classCalzonePizzaimplementsPizza{
    publicfloatgetPrice(){
        System.out.println("2.5f"
); return 2.5f; } }

建立工廠類PizzaFactory

通過傳入引數id,選擇不同的例項類,如果後續不斷的增加新類,會頻繁的修改create方法,不符合開閉原則


public interfacePizza{
    publicfloatgetPrice();
}

MargheritaPizza 類


public classMargheritaPizzaimplementsPizza{
    publicfloatgetPrice(){
        System.out.println("8.5f");
        return 8.5f;

    }
}

CalzonePizza 類


public classCalzonePizzaimplementsPizza{
    publicfloatgetPrice(){
        System.out.println("2.5f");
        return 2.5f;
    }


}

建立工廠類PizzaFactory

通過傳入引數id,選擇不同的例項類,如果後續不斷的增加新類,會頻繁的修改create方法,不符合開閉原則


public classPizzaFactory{
    public Pizza create(String id){
        if (id == null) {
          throw new IllegalArgumentException("id is null!");
        }
        if ("Calzone".equals(id)) {
          return new CalzonePizza();
        }

        if ("Margherita".equals(id)) {
          return new MargheritaPizza();
        }

        throw new IllegalArgumentException("Unknown id = " + id);
      }

}

使用annotation註解方式

註解方式減少對程式碼的侵入,避免xml配置的繁瑣,是spring高版喜歡使用的方式

建立ClassPathSpringScanner 掃描類

獲取當前classLoad下的所有class檔案


public class ClassPathSpringScanner {


    public static final String CLASS_SUFFIX = ".class";

    private ClassLoader classLoader = getClass().getClassLoader();


    public Set<Class<?>> getClassFile(String packageName) throws IOException {

        Map<String, String> classMap = new HashMap<>(32);
        String path = packageName.replace(".", "/");
        /**
         * 通過classLoader載入檔案,迴圈遍歷檔案,轉換class檔案
         */
        Enumeration<URL> urls = classLoader.getResources(path);

        while (urls!=null && urls.hasMoreElements()) {
            URL url = urls.nextElement();
            String protocol = url.getProtocol();
            /**
             * 如果是檔案
             */
            if ("file".equals(protocol)) {
                String file = URLDecoder.decode(url.getFile(), "UTF-8");
                File dir = new File(file);
                if(dir.isDirectory()){
                    parseClassFile(dir, classMap);
                }else {
                    throw new IllegalArgumentException("file must be directory");
                }
            } 
        }

        Set<Class<?>> set = new HashSet<>(classMap.size());
        for(String key : classMap.keySet()){
            String className = classMap.get(key);
            try {
                set.add(getClass().forName(className));
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return set;
    }

    /**
     * 遞迴演算法把class封裝到map集合裡
     * @param dir
     * @param packageName
     * @param classMap
     */
    protected void parseClassFile(File dir, Map<String, String> classMap){
        if(dir.isDirectory()){
            File[] files = dir.listFiles();
            for (File file : files) {
                parseClassFile(file, classMap);
            }
        } else if(dir.getName().endsWith(CLASS_SUFFIX)) {
            String name = dir.getPath();
            name = name.substring(name.indexOf("classes")+8).replace("\\", ".");
            addToClassMap(name, classMap);
        }
    }



    private boolean addToClassMap(String name, Map<String, String> classMap){
        if(!classMap.containsKey(name)){
            classMap.put(name, name.substring(0, name.length()-6)); //去掉.class
        }
        return true;
    }

只要被Factory註解過的類,都能通過beanId例項化物件。

@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.RUNTIME)
public @interface Factory {

    /**
     * 用來表示物件的唯一id
     */
    String id();
}

建立 BeanFactory 介面

public interface BeanFactory {
    public Object getBean(String id);
}

建立BeanFactory 的實現類AnnApplicationContext

將掃描後得到的class封裝到一個map裡,找出有被Factory註解的類,以beanId,class物件的鍵值對形式儲存。

public class AnnApplicationContext implements BeanFactory{



    private Map<String, Object> factoryClasses = new LinkedHashMap<String, Object>();

    private Set<Class<?>> classSet = new HashSet();


    ClassPathSpringScanner scanner = new ClassPathSpringScanner();
    /*
     * 建構函式初始化掃描獲取所有類
     */
    public AnnApplicationContext(String packageName) {

        try {
            //掃描classPath下的所有類,並返回set
            classSet = scanner.getClassFile(packageName);

            /**
             * 遍歷所有類,找出有factory註解的類,並封裝到linkedHashMap裡
             */
            for (Class<?> cls : classSet){
                Factory factory = (Factory) cls.getAnnotation(Factory.class);
                if(factory != null) 
                try {
                    factoryClasses.put(factory.id(), cls.newInstance());
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }

            }
        } catch (IOException e) {
            e.printStackTrace();
        }  
    }


    /**
     * 輸入的id,對應到factoryGroupedClasses的關係,例項化工廠物件
     * @param beanId
     * @return
     */
    @Override
    public Object getBean(String id) {

        return factoryClasses.get(id);
    }

MargheritaPizza 類

添加註釋Factory,定義beanId:Margherita

@Factory(id = "margherita")
public classMargheritaPizzaimplementsPizza{
    publicfloatgetPrice(){
        System.out.println("8.5f");
        return 8.5f;

    }
}

CalzonePizza 類

添加註釋Factory,定義beanId:Calzone

@Factory(id = "calzone")
public classCalzonePizzaimplementsPizza{
    publicfloatgetPrice(){
        System.out.println("2.5f");
        return 2.5f;
    }


}

測試下

publicstaticvoidmain(String[] args){
        /**
         * 掃描com.annotation.factory下的類
         */
        AnnApplicationContext factoryProcessor = new AnnApplicationContext("com.annotation.factory.spring");
        Pizza p= (Pizza) factoryProcessor.getBean("Calzone");
        p.getPrice();

    }

好了,看完程式碼應該很清楚了,註解是不是給我們帶來很多方便了。 留個思考題,如何預設通過類的名字,首個字母小寫來作為beanId



public interfacePizza{
    publicfloatgetPrice();
}

MargheritaPizza 類


public classMargheritaPizzaimplementsPizza{
    publicfloatgetPrice(){
        System.out.println("8.5f");
        return 8.5f;

    }
}

CalzonePizza 類


public classCalzonePizzaimplementsPizza{
    publicfloatgetPrice(){
        System.out.println("2.5f");
        return 2.5f;
    }


}

建立工廠類PizzaFactory

通過傳入引數id,選擇不同的例項類,如果後續不斷的增加新類,會頻繁的修改create方法,不符合開閉原則


public classPizzaFactory{
    public Pizza create(String id){
        if (id == null) {
          throw new IllegalArgumentException("id is null!");
        }
        if ("Calzone".equals(id)) {
          return new CalzonePizza();
        }

        if ("Margherita".equals(id)) {
          return new MargheritaPizza();
        }

        throw new IllegalArgumentException("Unknown id = " + id);
      }

}

使用annotation註解方式

註解方式減少對程式碼的侵入,避免xml配置的繁瑣,是spring高版喜歡使用的方式

建立ClassPathSpringScanner 掃描類

獲取當前classLoad下的所有class檔案


public class ClassPathSpringScanner {


    public static final String CLASS_SUFFIX = ".class";

    private ClassLoader classLoader = getClass().getClassLoader();


    public Set<Class<?>> getClassFile(String packageName) throws IOException {

        Map<String, String> classMap = new HashMap<>(32);
        String path = packageName.replace(".", "/");
        /**
         * 通過classLoader載入檔案,迴圈遍歷檔案,轉換class檔案
         */
        Enumeration<URL> urls = classLoader.getResources(path);

        while (urls!=null && urls.hasMoreElements()) {
            URL url = urls.nextElement();
            String protocol = url.getProtocol();
            /**
             * 如果是檔案
             */
            if ("file".equals(protocol)) {
                String file = URLDecoder.decode(url.getFile(), "UTF-8");
                File dir = new File(file);
                if(dir.isDirectory()){
                    parseClassFile(dir, classMap);
                }else {
                    throw new IllegalArgumentException("file must be directory");
                }
            } 
        }

        Set<Class<?>> set = new HashSet<>(classMap.size());
        for(String key : classMap.keySet()){
            String className = classMap.get(key);
            try {
                set.add(getClass().forName(className));
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return set;
    }

    /**
     * 遞迴演算法把class封裝到map集合裡
     * @param dir
     * @param packageName
     * @param classMap
     */
    protected void parseClassFile(File dir, Map<String, String> classMap){
        if(dir.isDirectory()){
            File[] files = dir.listFiles();
            for (File file : files) {
                parseClassFile(file, classMap);
            }
        } else if(dir.getName().endsWith(CLASS_SUFFIX)) {
            String name = dir.getPath();
            name = name.substring(name.indexOf("classes")+8).replace("\\", ".");
            addToClassMap(name, classMap);
        }
    }



    private boolean addToClassMap(String name, Map<String, String> classMap){
        if(!classMap.containsKey(name)){
            classMap.put(name, name.substring(0, name.length()-6)); //去掉.class
        }
        return true;
    }

只要被Factory註解過的類,都能通過beanId例項化物件。

@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.RUNTIME)
public @interface Factory {

    /**
     * 用來表示物件的唯一id
     */
    String id();
}

建立 BeanFactory 介面

public interface BeanFactory {
    public Object getBean(String id);
}

建立BeanFactory 的實現類AnnApplicationContext

將掃描後得到的class封裝到一個map裡,找出有被Factory註解的類,以beanId,class物件的鍵值對形式儲存。

public class AnnApplicationContext implements BeanFactory{



    private Map<String, Object> factoryClasses = new LinkedHashMap<String, Object>();

    private Set<Class<?>> classSet = new HashSet();


    ClassPathSpringScanner scanner = new ClassPathSpringScanner();
    /*
     * 建構函式初始化掃描獲取所有類
     */
    public AnnApplicationContext(String packageName) {

        try {
            //掃描classPath下的所有類,並返回set
            classSet = scanner.getClassFile(packageName);

            /**
             * 遍歷所有類,找出有factory註解的類,並封裝到linkedHashMap裡
             */
            for (Class<?> cls : classSet){
                Factory factory = (Factory) cls.getAnnotation(Factory.class);
                if(factory != null) 
                try {
                    factoryClasses.put(factory.id(), cls.newInstance());
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }

            }
        } catch (IOException e) {
            e.printStackTrace();
        }  
    }


    /**
     * 輸入的id,對應到factoryGroupedClasses的關係,例項化工廠物件
     * @param beanId
     * @return
     */
    @Override
    public Object getBean(String id) {

        return factoryClasses.get(id);
    }

MargheritaPizza 類

添加註釋Factory,定義beanId:Margherita

@Factory(id = "margherita")
public classMargheritaPizzaimplementsPizza{
    publicfloatgetPrice(){
        System.out.println("8.5f");
        return 8.5f;

    }
}

CalzonePizza 類

添加註釋Factory,定義beanId:Calzone

@Factory(id = "calzone")
public classCalzonePizzaimplementsPizza{
    publicfloatgetPrice(){
        System.out.println("2.5f");
        return 2.5f;
    }


}

測試下

publicstaticvoidmain(String[] args){
        /**
         * 掃描com.annotation.factory下的類
         */
        AnnApplicationContext factoryProcessor = new AnnApplicationContext("com.annotation.factory.spring");
        Pizza p= (Pizza) factoryProcessor.getBean("Calzone");
        p.getPrice();

    }

好了,看完程式碼應該很清楚了,註解是不是給我們帶來很多方便了。 留個思考題,如何預設通過類的名字,首個字母小寫來作為beanId