什么是链式接口(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. ksyun主机挂载ksyun硬盘

    1.查看虚拟磁盘的设备号是 /dev/vdc ls /dev/vd*2.格式化块设备,强烈推荐ext4文件系统: mkfs.ext4 /dev/vdc 3.准备挂载点,建立挂载目录.例: mkdir ...

  2. Siege(开源Web压力测试工具)——多线程编程最佳实例

    在英语中,"Siege"意为围攻.包围.同时Siege也是一款使用纯C语言编写的开源WEB压测工具,适合在GNU/Linux上运行,并且具有较强的可移植性.之所以说它是多线程编程的 ...

  3. 【Codeforces528D】Fuzzy Search FFT

    D. Fuzzy Search time limit per test:3 seconds memory limit per test:256 megabytes input:standard inp ...

  4. [转]Android使用Application总结

        目录(?)[+]   Application 配置全局Context 第一步.写一个全局的单例模式的MyApplication继承自Application 覆盖onCreate ,在这个方法里 ...

  5. Bootstrap_CSS概览

    在这一章中,我们将讲解 Bootstrap 底层结构的关键部分,包括我们让 web 开发变得更好.更快.更强壮的最佳实践. HTML 5 文档类型(Doctype) Bootstrap 使用了一些 H ...

  6. POJ 1470 Closest Common Ancestors (LCA, dfs+ST在线算法)

    Closest Common Ancestors Time Limit: 2000MS   Memory Limit: 10000K Total Submissions: 13370   Accept ...

  7. web语义化,从松散到实战

    GitHub:http://liu12fei08fei.github.io/html/4semantic.html web语义化,从松散到实战 在这篇文章之前,我放弃了很多次,写关于语义化方面的文章: ...

  8. superobject

    GITHUB: https://github.com/hgourvest/superobject # SuperObject ## What is JSON ? - JSON (JavaScript ...

  9. SWT 全接触

    http://www.ibm.com/developerworks/cn/opensource/os-swt/index.html 1.SWT简介 SWT-"Standard Widget ...

  10. vc中nmake.exe cl.exe 的使用

    首先简单介绍一下程序是如何编译链接的.程序写好之后,我们进行编译和链接来产生可执行程序.这时候,编译器为了完成编译和链接,需要知道很多信 息.比如要编译的文件是哪一个,使用哪些编译选项进行编译,编译好 ...