1. 程式人生 > >Spring Boot繫結列舉型別引數

Spring Boot繫結列舉型別引數

一、概述

在利用Spring進行Web後臺開發時,經常會遇到列舉型別的繫結問題。一般情況下,如果Spring接收到的引數值為字串型別,Spring會根據列舉的值與傳入的字串進行對應。假設有如下列舉

清單1:列舉定義

public enum Gender {
	MALE, FEMALE;
}

那麼,只要客戶端在傳送請求時,將引數的值設為MALE或FEMALE即可。請求類似如下形式:

http://localhost:8080/handle/enum?gender=MALE

但是,假如客戶端傳來的引數值不是列舉值對應的字串,而是諸如整數值之類的值,Spring就沒法做自動對應了。這種情況下該如何處理呢?

二、列舉與介面定義

好了,從現在開始,我們將使用如下列舉進行引數繫結。

清單2:需要進行轉換的列舉定義

package org.fhp.springbootdemo.entity;
 
import java.util.HashMap;
import java.util.Map;
 
public enum Gender implements BaseEnum {
	MALE(1), FEMALE(2);
 
	private int value;
	private static Map<Integer, Gender> valueMap = new HashMap<>();
	
	static {
		for(Gender gender : Gender.values()) {
			valueMap.put(gender.value, gender);
		}
	}
	
	Gender(int value) {
		this.value = value;
	}
	
	@Override
	public int getValue() {
		return value;
	}
 
	public static Gender getByValue(int value) {
		Gender result = valueMap.get(value);
		if(result == null) {
			throw new IllegalArgumentException("No element matches " + value);
		}
		return result;
	}
}

在這裡,我們令所有的列舉都實現BaseEnum介面,以便轉換時使用。BaseEnum介面定義如下:

清單3:列舉所需的實現介面

package org.fhp.springbootdemo.entity;
 
public interface BaseEnum {
	int getValue();
}

三、Converter介面
好在Spring為我們提供了一個型別自動轉換介面Converter<S, T>,可以實現從一個Object轉為另一個Object的功能。除了Converter介面之外,實現ConverterFactory介面和GenericConverter介面也可以實現我們自己的型別轉換邏輯。

我們先來看一下Converter介面的定義:

清單4:Converter介面定義
 

public interface Converter<S, T> {  
    T convert(S source); 
} 

我們可以看到這個介面是使用了泛型的,第一個型別表示原型別,第二個型別表示目標型別,然後裡面定義了一個convert方法,將原型別物件作為引數傳入進行轉換之後返回目標型別物件。當我們需要建立自己的converter的時候就可以實現該介面。
下面給出一個字串轉換為Gender列舉的converter實現。需要注意的是,在Spring MVC和Spring Boot中,由於從客戶端接收到的請求都被視為String型別,所以只能用String轉列舉的converter。

清單5:String轉Gender的Converter實現

package org.fhp.springbootdemo.converter;
 
 
import org.springframework.core.convert.converter.Converter;
 
 
public class StringToGenderConverter implements Converter<String, Gender> {
 
    @Override
    public Gender convert(String source) {
        return Gender.getByValue(Integer.parseInt(source));
    }
}

四、ConverterFactory介面
ConverterFactory的出現可以讓我們統一管理一些相關聯的Converter。顧名思義,ConverterFactory就是產生Converter的一個工廠,確實ConverterFactory就是用來產生Converter的。我們先來看一下ConverterFactory介面的定義:

清單6:ConverterFactory的介面定義
 

public interface ConverterFactory<S, R> {  
     
    <T extends R> Converter<S, T> getConverter(Class<T> targetType);  
   
}

我們可以看到ConverterFactory接口裡面就定義了一個產生Converter的getConverter方法,引數是目標型別的class。我們可以看到ConverterFactory中一共用到了三個泛型,S、R、T,其中S表示原型別,R表示目標型別,T是型別R的一個子類。
可以看出,ConverterFactory相比ConvertFactory的好處在於,ConverterFactory可以將原型別轉換成一組實現了相同介面型別的物件,而Converter則只能轉換成一種型別。這樣做的壞處在於,假如我們又定義了其他列舉,那麼對於每一個列舉,我們都需要實現一個對應的Converter,十分的不方便。而有了ConverterFactory之後,事情就變得簡單了不少。現在可以定義一個String到所有實現了BaseEnum的列舉的ConverterFactory,然後根據目標型別生成對應的Converter來進行轉換操作。如清單7所示。有了ConverterFactory,就可以取代清單5中的Converter了。

清單7:ConverterFactory轉換
 

package org.fhp.springbootdemo.converter;
 
import org.fhp.springbootdemo.entity.BaseEnum;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
 
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
 
public class UniversalEnumConverterFactory implements ConverterFactory<String, BaseEnum> {
 
    private static final Map<Class, Converter> converterMap = new WeakHashMap<>();
 
    @Override
    public <T extends BaseEnum> Converter<String, T> getConverter(Class<T> targetType) {
        Converter result = converterMap.get(targetType);
        if(result == null) {
            result = new IntegerStrToEnum<T>(targetType);
            converterMap.put(targetType, result);
        }
        return result;
    }
 
    class IntegerStrToEnum<T extends BaseEnum> implements Converter<String, T> {
        private final Class<T> enumType;
        private Map<String, T> enumMap = new HashMap<>();
 
        public IntegerStrToEnum(Class<T> enumType) {
            this.enumType = enumType;
            T[] enums = enumType.getEnumConstants();
            for(T e : enums) {
                enumMap.put(e.getValue() + "", e);
            }
        }
 
 
        @Override
        public T convert(String source) {
            T result = enumMap.get(source);
            if(result == null) {
                throw new IllegalArgumentException("No element matches " + source);
            }
            return result;
        }
    }
}
 

五、整合至Spring Boot

在Spring Boot中,可以通過覆蓋addFormatter方法來實現對Converter和ConverterFactory的繫結。

清單8:在配置中繫結ConverterFactory

package org.fhp.springbootdemo;
 
import org.fhp.springbootdemo.converter.UniversalEnumConverterFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
 
@Configuration
public class MyWebAppConfigurer extends WebMvcConfigurerAdapter {
 
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverterFactory(new UniversalEnumConverterFactory());
    }
}

當然,也可以通過registry.addConverter()方法來繫結Converter。

在Controller中,採用如下方式來進行接收,和平常接收引數是一樣的用法。

清單9:在Controller中的用法

package org.fhp.springbootdemo.controller;
 
import org.fhp.springbootdemo.entity.Gender;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.HashMap;
import java.util.Map;
 
@RestController
public class HandleEnumController {
 
    @RequestMapping("/handle/enum")
    public Object handleEnum(@RequestParam("gender") Gender gender) {
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("data", gender.name());
        return dataMap;
    }
}

 

參考:http://elim.iteye.com/blog/1860732#_Toc355638187

原文:https://blog.csdn.net/u014527058/article/details/62883573?utm_source=itdadao&utm_medium=referral