什么是链式接口(Fluent Interface)

根据wikipedia上的定义,Fluent interface是一种通过链式调用方法来完成方法的调用,其操作分为终结与中间操作两种。[1]

下面是一个Wikipedia上的例子。

Author author = AUTHOR.as("author");
create.selectFrom(author)
.where(exists(selectOne()
.from(BOOK)
.where(BOOK.STATUS.eq(BOOK_STATUS.SOLD_OUT))
.and(BOOK.AUTHOR_ID.eq(author.ID))));

在设置多参数时,通过这种方式简化操作,提高可读性。在effective java 第2版中讲述了这种接口的一个实现方式“第2条:遇到多个构造器参数时要考虑用构建器”。通过创建一个构建类Builder来实现这种风格的接口。

例如effective java 第2版,第2条的例子。

public class NutritionFacts {
private int servingSize;
private int fat; private NutritionFacts(Builder builder) {
this.servingSize = builder.servingSize;
this.fat = builder.fat;
}
...略
public static class Builder {
private int servingSize;
private int fat; public Builder servingSize(int sS) {
servingSize = sS;
return this;
} public Builder fat(int ft) {
fat = ft;
return this.
} public NutritionFacts build() {
return NutritionFacts(this);
}
}
}

可以看到,要使用这种风格的接口,那么每次都要创建Builder,然后实现属性设置,对象创建等重复操作。因此考虑通过程序自动完成这些重复操作,在使用时仅需要指定所需要实现的接口即可。

为了实现这种风格的操作需要完成两个主要的功能。 
1.链式调用设置属性。 
2.创建最终的对象。

通过Effective java 2 中的例子可以看到可以看到,链式调用的实现,是在调用属性设置方法时,每次返回Builder来实现,方法名根据要创建的目标对象的不同而变化,对于任何一个对象的创建都是通过build方法完成。

基于JDK动态代理的实现

下面以类Student为例。

public class Student {
private int age;
private String name;
private char sex;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Student [age=" + age + ", name=" + name + ", sex=" + sex + "]";
}
}

定义Builder接口,通过类型参数T来表示最终创建的对象,而方法build来完成最终对象的创建。

public interface Builder<T> {
T build();
}

创建Student的Builder接口,接口中的方法返回StudentBuilder最终实现链式属性设置。

public interface StudentBuilder extends Builder<Student>{
StudentBuilder age(int age);
StudentBuilder name(String name);
StudentBuilder sex(char sex);
}

最终的目标是,只需要使用者定义自己的XXXBuilder,而无需自己去实现各种属性设置方法及对象创建方法来使用链式调用完成属性设置。

接下来通过JDK的动态代理机制来实现上述功能。定义FluentApi来提供链式调用功能。通过target传入使用者定义的XXXBuilder接口。

public class FluentApi<T> {
/**
* 目标接口
*/
Class<T> target; private FluentApi(Class<T> target) {
if(!target.isInterface()) {
throw new IllegalArgumentException("must be interface");
}
this.target = target;
} public static <T> FluentApi<T> target(Class<T> target) {
return new FluentApi<T>(target);
}
}

接着创建XXXBuilder接口的代理。为了实现链式调用,每次必须返回代理对象proxy本身。

public class JdkProxy implements InvocationHandler{
private final String BUILDMETHOD = "build"; Class<?> target; public JdkProxy(Class<?> target) {
this.target = target;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName());
return proxy;
}
}

在FluentApi增加Create方法创建XXXBuilder的代理。

    public T create() {
JdkProxy jdkProxy = new JdkProxy(target);
return (T)Proxy.newProxyInstance(target.getClassLoader(),
new Class[]{target},
jdkProxy);
}

经过上面简单的两步,各个方法进行链式调用功能就完成了。

public class Main {

    public static void main(String[] args) {
FluentApi.target(StudentBuilder.class)
.create()
.age(1)
.sex('m')
.name("java")
.build();
}
}

接下来实现属性的设置与对象创建。由于对象创建是在build方法调用时创建,因此调用非build方法时仅保存属性。所以需要判断方法到底是属性设置还是对象创建。

build方法仅需要与Builder接口中build方法比较即可,判断方法如下。

    boolean isBuildMethod(Method method) {
try {
Method buildMethod = Builder.class.getDeclaredMethod(BUILDMETHOD);
return method.equals(buildMethod);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("");
}
}

属性设置方法满足的条件是只有一个参数且返回类型为使用者传入的接口类型,判断方法如下。

    boolean isPropertySetter(Method method) {
return method.getParameters().length == 1 && target.isAssignableFrom(method.getReturnType());
}

这样就可以在invoke方法中判断方法的类型了。

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName());
if(isPropertySetter(method)) {
System.out.println("PropertySetter");
return proxy;
}
else if(isBuildMethod(method)) {
System.out.println("BuildMethod");
return 最终要创建的对象;
}
return null;
}

接下来当满足isPropertySetter时,将属性保存,因此增加一个map来保存待设置的值。Map <String,Object> properties = new HashMap <String,Object> 。其中key为属性的名字,而value为属性值。由于在定义接口XXXBuilder时约定里面的属性设置方法是与要创建的对象的各个属性名保持一致,因此取方法名作为key即可,而属性参数值作为value。

最后调用build方法时,完成对象的创建。而为了创建对象,需要获取Builder<T>接口中的类型参数,这样才能完成对象的创建。

    Class<?> getParameterType(Class<?> clzz) {
Type[] types = clzz.getGenericInterfaces();
for(Type type : types) {
if(type instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType)type;
if(paramType.getRawType() == Builder.class) {
return (Class<?>)paramType.getActualTypeArguments()[0];
}
}
}
return null;
}

由于使用者传入的XXXBuilder实现了Builder<T>接口,因此只要检查XXXBuilder所实现的接口中哪个为参数类型ParameterizedType并且参数擦出后的原始类型为Builder即可。当类型满足这些条件时,再取出参数类型,这样就获取到了所需要的对象类型,就可以创建对象实例了[3]。

接下来将properties存放的属性赋给对象的各个成员变量即可。

    private void setPropperties(Object obj) throws Exception {
for(Map.Entry<String, Object> entry : properties.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
Field field = obj.getClass().getDeclaredField(key);
boolean isAccess = field.isAccessible();
try {
field.setAccessible(true);
field.set(obj, value);
}finally {
field.setAccessible(isAccess);
}
}
}

这样一个链式调用风格小框架主体功能就完成了,在使用时使用者仅需要定义属性设置接口,并满足方法名与属性名保持一致,随后就可以通过链式调用的方式来为对象设置属性并完成创建功能,而不需要重复编写属性设置与对象创建的代码了。

参考:

[1] fluent interface定义,https://en.wikipedia.org/wiki/Fluent_interface 
[2] effective java 第2版,第2条 
[3] jdk 1.8 api参考手册

代码:

public class FluentApi<T> {
/**
* 目标接口
*/
Class<T> target; private FluentApi(Class<T> target) {
if(!target.isInterface()) {
throw new IllegalArgumentException("must be interface");
}
this.target = target;
} public static <T> FluentApi<T> target(Class<T> target) {
return new FluentApi<T>(target);
} @SuppressWarnings("unchecked")
public T create() {
JdkProxy jdkProxy = new JdkProxy(target);
return (T)Proxy.newProxyInstance(target.getClassLoader(),
new Class[]{target},
jdkProxy);
}
}
public class JdkProxy implements InvocationHandler{
private final String BUILDMETHOD = "build"; Class<?> target; Class<?> object;
Map<String,Object> properties = new HashMap<String,Object>(); public JdkProxy(Class<?> target) {
this.target = target;
this.object = getParameterType(target);
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName());
if(isPropertySetter(method)) {
properties.put(method.getName(), args[0]);
System.out.println("PropertySetter");
return proxy;
}
else if(isBuildMethod(method)) {
Object obj = object.newInstance();
setPropperties(obj);
System.out.println("BuildMethod");
return obj;
}
return null;
} boolean isPropertySetter(Method method) {
return method.getParameters().length == 1 && target.isAssignableFrom(method.getReturnType());
} boolean isBuildMethod(Method method) {
try {
Method buildMethod = Builder.class.getDeclaredMethod(BUILDMETHOD);
return method.equals(buildMethod);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("");
}
} private Class<?> getParameterType(Class<?> clzz) {
Type[] types = clzz.getGenericInterfaces();
for(Type type : types) {
if(type instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType)type;
if(paramType.getRawType() == Builder.class) {
return (Class<?>)paramType.getActualTypeArguments()[0];
}
}
}
return null;
} private void setPropperties(Object obj) throws Exception {
for(Map.Entry<String, Object> entry : properties.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
Field field = obj.getClass().getDeclaredField(key);
boolean isAccess = field.isAccessible();
try {
field.set(obj, value);
field.setAccessible(true);
}finally {
field.setAccessible(isAccess);
}
}
}
}
public interface Builder<T> {
T build();
} public interface StudentBuilder extends Builder<Student>{
StudentBuilder age(int age);
StudentBuilder name(String name);
StudentBuilder sex(char sex);
}
public class Main {

    public static void main(String[] args) {
Student student =
FluentApi.target(StudentBuilder.class)
.create()
.age(1)
.sex('m')
.name("java")
.build();
System.out.println(student);
}
}

基于JDK动态代理实现的接口链式调用(Fluent Interface)工具的更多相关文章

  1. 设计模式之jdk动态代理模式、责任链模式-java实现

    设计模式之JDK动态代理模式.责任链模式 需求场景 当我们的代码中的类随着业务量的增大而不断增大仿佛没有尽头时,我们可以考虑使用动态代理设计模式,代理类的代码量被固定下来,不会随着业务量的增大而增大. ...

  2. 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。

    基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别. 我还是喜欢基于Schema风格的Spring事务管理,但也有很多人在用基于@Tras ...

  3. Spring -- <tx:annotation-driven>注解基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)的区别。

    借鉴:http://jinnianshilongnian.iteye.com/blog/1508018 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional ...

  4. SpringBoot27 JDK动态代理详解、获取指定的类类型、动态注册Bean、接口调用框架

    1 JDK动态代理详解 静态代理.JDK动态代理.Cglib动态代理的简单实现方式和区别请参见我的另外一篇博文. 1.1 JDK代理的基本步骤 >通过实现InvocationHandler接口来 ...

  5. JDK 动态代理分析

    Java的代理有两种:静态代理和动态代理,动态代理又分为 基于jdk的动态代理 和 基于cglib的动态代理 ,两者都是通过动态生成代理类的方法实现的,但是基于jdk的动态代理需要委托类实现接口,基于 ...

  6. 从静态代理,jdk动态代理到cglib动态代理-一文搞懂代理模式

    从代理模式到动态代理 代理模式是一种理论上非常简单,但是各种地方的实现往往却非常复杂.本文将从代理模式的基本概念出发,探讨代理模式在java领域的应用与实现.读完本文你将get到以下几点: 为什么需要 ...

  7. 动态代理之 JDK 动态代理

    动态代理 动态代理源于设计模式中的代理模式,代理模式的主要作用就是使代理对象完成用户的请求,屏蔽用户对真实对象的访问.通过代理对象去访问目标对象来控制原对象的访问. 代理模式的最典型的应用就是 Spr ...

  8. 面试造火箭系列,栽在了cglib和jdk动态代理

    "喂,你好,我是XX巴巴公司的技术面试官,请问你是张小帅吗".声音是从电话那头传来的 "是的,你好".小帅暗喜,大厂终于找上我了. "下面我们来进行一 ...

  9. 017 Java中的静态代理、JDK动态代理、cglib动态代理

    一.静态代理 代理模式是常用设计模式的一种,我们在软件设计时常用的代理一般是指静态代理,也就是在代码中显式指定的代理. 静态代理由业务实现类.业务代理类两部分组成.业务实现类负责实现主要的业务方法,业 ...

随机推荐

  1. 鸟哥的私房菜:Linux磁盘与文件系统原理

    1 硬盘物理组成     //原理 磁头负责读写     磁道(硬盘同半径的一圈) 磁柱(所有盘磁道叠加起来的柱)     扇区(2条半径将磁道分开的一个扇形区域,是磁盘的最小存储单位) ------ ...

  2. python 爬虫 处理超级课程表传输的数据

    借鉴的别人的思路 http://www.oschina.net/code/snippet_2463131_53711 抓取超级课程表传输的数据 他的传输数据居然是明文的-- 现在已经把自己的课表都抓出 ...

  3. JS简单实现二级联动菜单

    <form method="post" action=""> 省/市:<select id="province" onch ...

  4. 用zrender实现工作流图形化设计(附范例代码)

    公司研发的管理系统有工作流图形化设计和查看功能,这个功能的开发历史比较久远.在那个暗无天日的年月里,IE几乎一统江湖,所以顺理成章地采用了当时红极一时的VML技术. 后来的事情大家都知道了,IE开始走 ...

  5. apache上部署django的静态文件

    一直在优化自己博客的代码, 昨天把css样式表分离出来, 用作静态 文件, 但是自己还没学django怎么使用静态文件, 经过一番google 终于解决了. django 使用静态文件有两种方法, 一 ...

  6. linux 定时任务 crontab 详细解释(转)

    cron 是linux的内置服务,但它不自动起来,可以用以下的方法启动.关闭这个服务:  引用:  /sbin/service crond start //启动服务  /sbin/service cr ...

  7. VPP电源控制(VPP Power)-- 由DC-DC变换集成电路MC34063组成

    http://www.willar.com/article/article_view.asp?id=463 由DC-DC变换集成电路MC34063组成,34063 广泛应用于于DC-DC的电源转换电路 ...

  8. Linux gcc编译参数

    最近编译一份开源代码,一编译就直接报错.我看了下报错信息,有点诧异.这些信息,放平常顶多就是个warnning而已啊,他这里怎么变成了error呢?我看了下Makefile,发现编译参数多了个-Wer ...

  9. android手机安全:被攻陷的一个场景

     到处找WIFI,对于我们的手机控来说是相当普遍的了.假设你发现了有可用的wifi,并选择了浏览器连接,当浏览器出现一个web 页面的时候,你可能已经中招了. 相同,当你的手机使用一些免费应用的时候, ...

  10. python定位性能的工具

    参考: http://www.ibm.com/developerworks/cn/linux/l-cn-python-optim/ http://xianglong.me/article/analys ...