作者: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

JavaSE实现IoC的更多相关文章

  1. Spring之IoC总结帖

    Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development a ...

  2. Spring 实践 -IoC

    Spring 实践 标签: Java与设计模式 Spring简介 Spring是分层的JavaSE/EE Full-Stack轻量级开源框架.以IoC(Inverse of Control 控制反转) ...

  3. Spring框架之IOC(控制反转)

    [TOC] 第一章Spring框架简介 IOC(控制反转)和AOP(面向方面编程)作为Spring框架的两个核心,很好地实现了解耦合.所以,简单来说,Spring是一个轻量级的控制反转(IoC)和面向 ...

  4. ioc(Inversion of Control)控制反转和DI

    ioc意味着将你设计好的交给容器控制,而不是传统在你的对象中直接控制 谁控制了谁:传统的javaSE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象:而ioc是有专门一个容 ...

  5. 框架学习之Spring(一IOC)----HelloWrod

    一.概述 Spring是一个开源框架,它的核心是控制反转(IOC)和面向切面(AOP).简单来说,Spring是一个分层的JavaSE/EEfull-stack(一站式)轻量级开源框架. EE 开发分 ...

  6. Spring第一天——入门与IOC

    大致内容 spring基本概念 IOC入门 [17.6.9更新],如何学习spring? 掌握用法 深入理解 不断实践 反复总结 再次深入理解与实践 一.Spring相关概念  1.概述: Sprin ...

  7. Spring框架-IOC和AOP简单总结

    参考博客: https://blog.csdn.net/qq_22583741/article/details/79589910 1.Spring框架是什么,为什么,怎么用 1.1 Spring框架是 ...

  8. Spring系列之IOC的原理及手动实现

    目录 Spring系列之IOC的原理及手动实现 Spring系列之DI的原理及手动实现 导语 Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架.也是几乎所有J ...

  9. Spring 框架学习—控制反转(IOC)

        Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson创建. 简单来说,Spring是一个分层的JavaSE/EEfull-st ...

随机推荐

  1. HDU-6703 array (线段树)

    题意 一个长度为n的排列a,\(\forall i\in [1,n] ,1\le a_i \le n\) , m次操作,每次操作: (1,pos),把 \(a_{pos}\) 变为\(a_{pos} ...

  2. trie浅谈

    关于trie ​ 其实字典树和以上两种算法有很大不同,但是hash由于其优秀的应用,导致有些字符串查找用hash也是可行的. ​ 字典树中支持添加,查找,区间查询(可持久化字典树),而且在异或操作上有 ...

  3. Codeforces Round #652 (Div. 2) E. DeadLee(贪心)

    题目链接:https://codeforces.com/contest/1369/problem/E 题意 Lee 有 $n$ 种不同种类的食物和 $m$ 个朋友,每种食物有 $w_i$ 个,每个朋友 ...

  4. zjnu1749 PAROVI (数位dp)

    Description The distance between two integers is defined as the sum of the absolute result of subtra ...

  5. EF Core数据访问入门

    重要概念 Entity Framework (EF) Core 是轻量化.可扩展.开源和跨平台的数据访问技术,它还是一 种对象关系映射器 (ORM),它使 .NET 开发人员能够使用面向对象的思想处理 ...

  6. Linux ulimit使用

    什么是ulimit? ulimit是一个可以设置或者汇报当前用户资源限制的命令.使用ulimit命令需要有管理员权限,它只能在允许使用shell进行控制的系统中使用.也就是说它已经被嵌入到shell当 ...

  7. Python——Django框架——邮件

    #引用EMAIL服务 EMAIL_BACKEND = 'django.cpre.mail.backends.smtp.EmailBackend' #EMAIL发送服务器 EMAIL_HOST = 's ...

  8. PAT L2-004. 这是二叉搜索树吗?【前序遍历转化为后序遍历】

    一棵二叉搜索树可被递归地定义为具有下列性质的二叉树:对于任一结点, 其左子树中所有结点的键值小于该结点的键值: 其右子树中所有结点的键值大于等于该结点的键值: 其左右子树都是二叉搜索树. 所谓二叉搜索 ...

  9. 域渗透——获得域控服务器的NTDS.dit文件

    0x00 前言 在之前的文章<导出当前域内所有用户hash的技术整理>曾介绍过通过Volume Shadow Copy实现对ntds.dit文件的复制, 可用来导出域内所有用户hash.本 ...

  10. 二分类问题中混淆矩阵、PR以及AP评估指标

    仿照上篇博文对于混淆矩阵.ROC和AUC指标的探讨,本文简要讨论机器学习二分类问题中的混淆矩阵.PR以及AP评估指标:实际上,(ROC,AUC)与(PR,AP)指标对具有某种相似性. 按照循序渐进的原 ...