JavaSE實現IoC

作者:Grey

原文地址:

語雀

部落格園

Java SE 提供了三種方式,可以實現IoC,分別為:

  • Java Beans
  • Java ServiceLoader SPI
  • JNDI(Java Naming and Directory Interface)

Java Beans 方式

java.beans包下的 Introspector 類提供了一個 getBeanInfo的方法,可以獲取一個類的資訊

BeanInfo bi=Introspector.getBeanInfo(User.class,Object.class);

如上,則可以獲取User類對象的BeanInfo, 然後我們通過BeanInfo中的 getPropertyDescriptors 方法,可以獲取到User對象中的所有屬性和方法,

注意:java beans中,對於set(xxx)方法,統一叫:writeMethod(), 對於get() 方法,統一叫:readMethod()

Stream.of(bi.getPropertyDescriptors()).forEach(pd->{
        Class<?> propertyType=pd.getPropertyType();
        Method writeMethod=pd.getWriteMethod();
        });

獲取到方法和屬性名稱後,通過反射即可把對應的值設置到對應的屬性中

writeMethod.invoke(name,value);

由於我們注入屬性值的時候,我們注入的東西永遠是一個字元串類型,如果需要注入的屬性是其他類型(非字元串), 比如User類中,有一個屬性是address,這個address是一個對象類型,

我們應該如何定義一個轉換器,將字元串類型的值轉換為我們需要的對象類型呢?

我們需要通過設置一個AddressEditor來實現這個轉換,這個AddressEditor有兩種實現方式, 一是實現PropertyEditor介面,另外一種方式是繼承PropertyEditorSupport類,
由於我們只需要實現一些簡單的轉換,PropertyEditorSupport提供了更為便利的實現方式,所以我們採用繼承PropertyEditorSupport類的方法,來實現類型的轉換,

Address類的設計是:

public class Address {
    private String name;
    private Integer num;
    // 省略 get / set / toString
}

我們的定義的規則如下,

輸入的字元串用|來分割 name 和 num屬性

例如: 「貝克街|221」 這個字元串 會將「貝克街」賦給name,221賦給num

public class AddressEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        String[] tokens = text.split("\\|");
        Address address = new Address();
        address.setName(tokens[0]);
        address.setNum(Integer.valueOf(tokens[1]));
        setValue(address);
    }
}

但是我們需要重寫setAsText方法,即:將字元串類型按照我們定義的規則轉換成對應需要的類型即可,同理,我們可以實現一個DateEditor,讓「yyyy-MM-dd」這樣類型的字元串轉換成日期格式。

public class DateEditor extends PropertyEditorSupport {

    static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        LocalDate localDate = LocalDate.parse(text, dtf);
        ZoneId zone = ZoneId.systemDefault();
        Instant instant = localDate.atStartOfDay().atZone(zone).toInstant();
        setValue(Date.from(instant));
    }
}

然後,我們需要使用java beans中的PropertyEditorManager類的registerEditor方法把這兩個Editor註冊進來

registerEditor(Address.class,AddressEditor.class);
registerEditor(Date.class,DateEditor.class);

最後,PropertyEditorManager的findEditor方法就可以根據我們前面得到的屬性類型,找到對應的Editor來對值進行轉換,轉換成我們需要的屬性類型的值

PropertyEditor editor = findEditor(propertyType);
if (editor != null) {
    editor.setAsText(parameters.get(pd.getName()));
    try {
        writeMethod.invoke(user, editor.getValue());
    } catch (IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
} else {
    System.out.println("no editor for:" + pd.getName());
}

主函數調用示例

public static void main(String[] args) throws Exception {
    Map<String, String> parameters = new HashMap<String, String>() {
        {
            //這裡的key要和Node裡面的屬性名一致
            put("name", "福爾摩斯");
            put("address", "貝克街|221");
            put("birthday", "1854-01-06");
        }
    };
    User convert = PropertyEditorSample.convert(parameters);
    System.out.println(convert);
}

運行結果

User{name='福爾摩斯', birthday=Thu Jan 05 23:54:17 CST 1854, address=Address{name='貝克街, 221 號}}

SPI方式

定義支付介面PayService

public interface PayService {
    void pay();
}

定義多個實現:

public class WeixinpayService implements PayService{
    @Override
    public void pay() {
        System.out.println("微信支付");
    }
}
public class AlipayService implements PayService{
    @Override
    public void pay() {
        System.out.println("支付寶支付");
    }
}

在resources目錄下建立META-INF文件夾,在META-INF文件夾下建立services目錄,同時建立一個文件,名稱為介面的全路徑名,以這個項目為例, PayService的全路徑名稱為:

org.snippets.ioc.java.spi.PayService

在這個文件內,把實現類的全路徑名寫進去:

org.snippets.ioc.java.spi.AlipayService
org.snippets.ioc.java.spi.WeixinpayService

客戶端調用:

ServiceLoader<PayService> serviceLoader = ServiceLoader.load(PayService.class);

for (PayService ele : serviceLoader) {
    ele.pay();
}

其中ServiceLoader.load方法可以把所有配置的PayService實現得到

執行結果:

支付寶支付
微信支付

JNDI方式

定義一個Person類

public class Person implements Remote, Serializable {

    private static final long serialVersionUID = 1L;
    private String name;
    private String password;

   // 省略set / get方法
}

實現JNDI的客戶端,實現初始化Person和查找Person兩個功能

public static void initPerson() throws Exception {
        //配置JNDI工廠和JNDI的url和埠。如果沒有配置這些資訊,將會出現NoInitialContextException異常
        LocateRegistry.createRegistry(3000);
        System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
        System.setProperty(Context.PROVIDER_URL, "rmi://localhost:3000");


        InitialContext ctx = new InitialContext();

        //實例化person對象
        Person p = new Person();
        p.setName("zc");
        p.setPassword("123");

        //將person對象綁定到JNDI服務中,JNDI的名字叫做:person。
        ctx.bind("person", p);
        ctx.close();
}

public static void findPerson() throws Exception {
    //因為前面已經將JNDI工廠和JNDI的url和埠已經添加到System對象中,這裡就不用在綁定了
    InitialContext ctx = new InitialContext();
    
    //通過lookup查找person對象
    Person person = (Person) ctx.lookup("person");
    
    //列印出這個對象
    System.out.println(person.toString());
    ctx.close();
}

完整程式碼

Github

參考資料

小馬哥講Spring核心編程思想

PropertyEditorSupport的使用

Java SPI機制 – ServiceLoader

02_Java通訊_JNDI_demo1

Tags: