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();
}