JavaSE實現IoC

做者:Greyhtml

原文地址:java

語雀git

博客園github

Java SE 提供了三種方式,能夠實現IoC,分別爲:spring

  • 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對象中的全部屬性和方法,json

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

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

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

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

相關文章
相關標籤/搜索