Java接口外观

Java Interface Facades

Introduction

This article discusses extending final implementation classes through the use of Proxy InvocationHandlers and Default Interface Methods introduced in Java 8. The specific use case described here is to add fluent methods to Document Object Model (DOM) to enable Javadoc Ťaglet implementations to provide snippets of well-formed HTML/XML. The various fluent add() methods implemented in FluentNode (the "facade"):

    default FluentNode add(Stream<Node> stream) {
        return add(stream.toArray(Node[]::new));
    }

    default FluentNode add(Iterable<Node> iterable) {
        return add(toArray(iterable));
    }

    default FluentNode add(Node... nodes) {
        for (Node node : nodes) {
            switch (node.getNodeType()) {
            case ATTRIBUTE_NODE:
                getAttributes().setNamedItem(node);
                break;

            default:
                appendChild(node);
                break;
            }
        }

        return this;
    }

which allows the creation of fluent methods to create Elements:

    default FluentNode element(String name, Stream<Node> stream) {
        return element(name, stream.toArray(Node[]::new));
    }

    default FluentNode element(String name, Iterable<Node> iterable) {
        return element(name, toArray(iterable));
    }

    default FluentNode element(String name, Node... nodes) {
        return ((FluentNode) owner().createElement(name)).add(nodes);
    }

that can be built up in to templates (e.g., HTMLTemplates):

    default FluentNode pre(String content) {
        return element("pre").content(content);
    }

允许平凡地调用方法的创建以返回XML代码段:

        return a(href, text)
                   .add(map.entrySet()
                        .stream()
                        .map(t -> attr(t.getKey(), t.getValue())));

产生类似

<a href="https://www.rfc-editor.org/rfc/rfc2045.txt" target="newtab">RFC2045</a>

Complete javadoc is provided.

Theory of Operation

An application can add a facade to a class hierarchy by extending FacadeProxyInvocationHandler1 and implementing getProxyClassFor(Object) where the invoke(Object,Method,Object[]) "enhances" any eligible return types. Conceptually:

    public Object enhance(Object in) {
        Object out = null;
        Class<?> type = getProxyClassFor(in);

        if (type != null) {
            try {
                out =
                    type.getConstructor(InvocationHandler.class)
                    .newInstance(this);
            } catch (RuntimeException exception) {
                throw exception;
            } catch (Exception exception) {
                throw new IllegalStateException(exception);
            }
        }

        return (out != null) ? out : in;
    }

    protected abstract Class<?> getProxyClassFor(Object object);

    @Override
    public Object invoke(Object proxy, Method method, Object[] argv) throws Throwable {
        Object result = super.invoke(proxy, method, argv);

        return enhance(result);
    }

There are additional details that are discussed in the next section. The implementor must return a interface Class to Proxy from getProxyClassFor(Object) for any Class to be enhanced.

Implementation

Node will be enhanced by Fluentñode and Document will be enhanced by FluentDocument. Note: A ñode does not necessarily have to implement the sub-interface that corresponds to ñode.getNodeType() so both the Object's class hierarchy and node type are analyzed and the results are cached in the getProxyClassFor(Object) implementation.

        private final HashMap<List<Class<?>>,Class<?>> map = new HashMap<>();
        ...
        @Override
        protected Class<?> getProxyClassFor(Object object) {
            Class<?> type = null;

            if (object instanceof Node && (! (object instanceof FluentNode))) {
                Node node = (Node) object;
                List<Class<?>> key =
                    Arrays.asList(NODE_TYPE_MAP.getOrDefault(node.getNodeType(), Node.class),
                                  node.getClass());

                type = map.computeIfAbsent(key, k -> compute(k));
            }

            return type;
        }

        private Class<?> compute(List<Class<?>> key) {
            LinkedHashSet<Class<?>> implemented =
                key.stream()
                .flatMap(t -> getImplementedInterfacesOf(t).stream())
                .filter(t -> Node.class.isAssignableFrom(t))
                .filter(t -> Node.class.getPackage().equals(t.getPackage()))
                .collect(Collectors.toCollection(LinkedHashSet::new));
            LinkedHashSet<Class<?>> interfaces =
                implemented.stream()
                .map(t -> fluent(t))
                .filter(Objects::nonNull)
                .collect(Collectors.toCollection(LinkedHashSet::new));

            interfaces.addAll(implemented);

            new ArrayList<>(interfaces)
                .stream()
                .forEach(t -> interfaces.removeAll(Arrays.asList(t.getInterfaces())));

            return getProxyClass(interfaces.toArray(new Class<?>[] { }));
        }

The corresponding "fluent" interface is found through reflection:

        private Class<?> fluent(Class<?> type) {
            Class<?> fluent = null;

            if (Node.class.isAssignableFrom(type) && Node.class.getPackage().equals(type.getPackage())) {
                try {
                    String name =
                        String.format("%s.Fluent%s",
                                      FluentNode.class.getPackage().getName(),
                                      type.getSimpleName());

                    fluent = Class.forName(name).asSubclass(FluentNode.class);
                } catch (Exception exception) {
                }
            }

            return fluent;
        }

The DocumentBuilderFactory, FluentDocumentBuilderFactory, and DocumentBuilder, FluentDocument.Builder, implementations are both straightforward. The two DocumentBuilder methods that create new Documents are implemented by creating a new FluentNode.InvocationHandler:

        @Override
        public FluentDocument newDocument() {
            Document document = builder.newDocument();

            return (FluentDocument) new FluentNode.InvocationHandler().enhance(document);
        }

        @Override
        public Document parse(InputSource in) throws SAXException, IOException {
            Document document = builder.parse(in);

            return (FluentDocument) new FluentNode.InvocationHandler().enhance(document);
        }

创建一个新的流利的文件很简单:

            document =
                FluentDocumentBuilderFactory.newInstance()
                .newDocumentBuilder()
                .newDocument();

不幸的是,到目前为止所描述的实现将失败,并显示类似以下错误:

...
[ERROR] Caused by: org.w3c.dom.DOMException: WRONG_DOCUMENT_ERR: A node is used in a different document than the one that created it.
[ERROR]     at com.sun.org.apache.xerces.internal.dom.AttributeMap.setNamedItem(AttributeMap.java:86)
[ERROR]     at ball.xml.FluentNode.add(FluentNode.java:180)
...
[ERROR]     ... 35 more
...

The com.sun.org.apache.xerces.internal.dom implementation classes expect to have package access to other package classes. This requires adjusting the invoke(Object,Method,Object[]) implementation to choose the wider of the Proxy facade or the reverse depending on the required context:

    @Override
    public Object invoke(Object proxy, Method method, Object[] argv) throws Throwable {
        Object result = null;
        Class<?> declarer = method.getDeclaringClass();
        Object that = map.reverse.get(proxy);

        if (declarer.isAssignableFrom(Object.class)) {
            result = method.invoke(that, argv);
        } else {
            argv = reverseFor(method.getParameterTypes(), argv);

            if (declarer.isAssignableFrom(that.getClass())) {
                result = method.invoke(that, argv);
            } else {
                result = super.invoke(proxy, method, argv);
            }
        }

        return enhance(result);
    }

This requires keeping an IdentityHashMap of enhanced Object to Proxy and reverse:

    private final ProxyMap map = new ProxyMap();

    public Object enhance(Object in) {
        Object out = null;

        if (! hasFacade(in)) {
            Class<?> type = getProxyClassFor(in);

            if (type != null) {
                out = map.computeIfAbsent(in, k -> compute(type));
            }
        }

        return (out != null) ? out : in;
    }

    private <T> T compute(Class<T> type) {
        T proxy = null;

        try {
            proxy =
                type.getConstructor(InvocationHandler.class)
                .newInstance(this);
        } catch (RuntimeException exception) {
            throw exception;
        } catch (Exception exception) {
            throw new IllegalStateException(exception);
        }

        return proxy;
    }

    private class ProxyMap extends IdentityHashMap<Object,Object> {
        private final IdentityHashMap<Object,Object> reverse = new IdentityHashMap<>();

        public IdentityHashMap<Object,Object> reverse() { return reverse; }

        @Override
        public Object put(Object key, Object value) {
            reverse().put(value, key);

            return super.put(key, value);
        }
    }

and providing the necessary "reverse" methods contained in the source.

Integration

AbstractTaglet demonstrates the integration. The class must implement XMLServices and provide an implementation of document().

    private final FluentDocument document;
    ...
    protected AbstractTaglet(boolean isInlineTag, boolean inPackage,
                             boolean inOverview, boolean inField,
                             boolean inConstructor, boolean inMethod,
                             boolean inType) {
        ...
        try {
            ...
            document =
                FluentDocumentBuilderFactory.newInstance()
                .newDocumentBuilder()
                .newDocument();
            document
                .add(element("html",
                             element("head",
                                     element("meta",
                                             attr("charset", "utf-8"))),
                             element("body")));
        } catch (Exception exception) {
            throw new ExceptionInInitializerError(exception);
        }
    }
    ...
    @Override
    public FluentDocument document() { return document; }

一种bstractTaglet also implements HTMLTemplates which provides default methods for HTML elements/nodes. HTMLTemplates is further extended by JavadocHTMLTemplates to provide common HTML/XML fragments required to generate Javadoc.

Summary

The FacadeProxyInvocationHandler combined with specialized interfaces implementing "default" methods provides a mechanism for extending an otherwise final class hierarchy.


  1. FacadeProxyInvocationHandler is a subclass of DefaultInvocationHandler whose (invoke(Object,Method,Object[]) implementation is discussed in "Adding Support to Java InvocationHandler Implementations for Interface Default Methods"). 

from: https://dev.to//allenball/java-interface-facades-38ij

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值