动态代理
_代理_是基本的设计模式之一。一个对象封装真实对象,代替其提供其他或不同的操作—这些操作通常涉及到与“真实”对象的通信,因此代理通常充当中间对象。这是一个简单的示例,显示代理的结构:
interface Interface {
void doSomething();
void somethingElse(String arg);
}
class RealObject implements Interface {
@Override
public void doSomething() {
System.out.println("doSomething");
}
@Override
public void somethingElse(String arg) {
System.out.println("somethingElse " + arg);
}
}
class SimpleProxy implements Interface {
private Interface proxied;
SimpleProxy(Interface proxied) {
this.proxied = proxied;
}
@Override
public void doSomething() {
System.out.println("SimpleProxy doSomething");
proxied.doSomething();
}
@Override
public void somethingElse(String arg) {
System.out.println(
"SimpleProxy somethingElse " + arg);
proxied.somethingElse(arg);
}
}
class SimpleProxyDemo {
public static void consumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}
public static void main(String[] args) {
consumer(new RealObject());
consumer(new SimpleProxy(new RealObject()));
}
}
输出结果:
因为 consumer()
接受 Interface
,所以它不知道获得的是 RealObject
还是 SimpleProxy
,因为两者都实现了 Interface
。
但是,在客户端和 RealObject
之间插入的 SimpleProxy
执行操作,然后在 RealObject
上调用相同的方法。
当你希望将额外的操作与“真实对象”做分离时,代理可能会有所帮助,尤其是当你想要轻松地启用额外的操作时,反之亦然(设计模式就是封装变更—所以你必须改变一些东西以证明模式的合理性)。例如,如果你想跟踪对 RealObject
中方法的调用,或衡量此类调用的开销,该怎么办?你不想这部分代码耦合到你的程序中,而代理能使你可以很轻松地添加或删除它。
Java 的_动态代理_更进一步,不仅动态创建代理对象而且动态处理对代理方法的调用。在动态代理上进行的所有调用都被重定向到单个_调用处理程序_,该处理程序负责发现调用的内容并决定如何处理。这是 SimpleProxyDemo.java
使用动态代理重写的例子:
import java.lang.reflect.*;
class DynamicProxyHandler implements InvocationHandler {
private Object proxied;
DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}
@Override
public Object
invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println(
"**** proxy: " + proxy.getClass() +
", method: " + method + ", args: " + args);
if (args != null) {
for (Object arg : args) {
System.out.println(" " + arg);
}
}
return method.invoke(proxied, args);
}
}
class SimpleDynamicProxy {
public static void consumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}
public static void main(String[] args) {
RealObject real = new RealObject();
consumer(real);
// Insert a proxy and call again:
Interface proxy = (Interface) Proxy.newProxyInstance(
Interface.class.getClassLoader(),
new Class[]{Interface.class},
new DynamicProxyHandler(real));
consumer(proxy);
}
}
输出结果:
可以通过调用静态方法 Proxy.newProxyInstance()
来创建动态代理,该方法需要一个类加载器(通常可以从已加载的对象中获取),希望代理实现的接口列表(不是类或抽象类),以及接口 InvocationHandler
的一个实现。动态代理会将所有调用重定向到调用处理程序,因此通常为调用处理程序的构造函数提供对“真实”对象的引用,以便一旦执行中介任务便可以转发请求。
invoke()
方法被传递给代理对象,以防万一你必须区分请求的来源—但是在很多情况下都无需关心。但是,在 invoke()
内的代理上调用方法时要小心,因为接口的调用是通过代理重定向的。
通常执行代理操作,然后使用 Method.invoke()
将请求转发给被代理对象,并携带必要的参数。这在一开始看起来是有限制的,好像你只能执行一般的操作。但是,可以过滤某些方法调用,同时传递其他方法调用:
import java.lang.reflect.*;
class MethodSelector implements InvocationHandler {
private Object proxied;
MethodSelector(Object proxied) {
this.proxied = proxied;
}
@Override
public Object
invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (method.getName().equals("interesting")) {
System.out.println(
"Proxy detected the interesting method");
}
return method.invoke(proxied, args);
}
}
interface SomeMethods {
void boring1();
void boring2();
void interesting(String arg);
void boring3();
}
class Implementation implements SomeMethods {
@Override
public void boring1() {
System.out.println("boring1");
}
@Override
public void boring2() {
System.out.println("boring2");
}
@Override
public void interesting(String arg) {
System.out.println("interesting " + arg);
}
@Override
public void boring3() {
System.out.println("boring3");
}
}
class SelectingMethods {
public static void main(String[] args) {
SomeMethods proxy =
(SomeMethods) Proxy.newProxyInstance(
SomeMethods.class.getClassLoader(),
new Class[]{SomeMethods.class},
new MethodSelector(new Implementation()));
proxy.boring1();
proxy.boring2();
proxy.interesting("bonobo");
proxy.boring3();
}
}
输出结果:
在这个示例里,我们只是在寻找方法名,但是你也可以寻找方法签名的其他方面,甚至可以搜索特定的参数值。
动态代理不是你每天都会使用的工具,但是它可以很好地解决某些类型的问题。你可以在 Erich Gamma 等人的_设计模式_中了解有关_代理_和其他设计模式的更多信息。
Optional类
如果你使用内置的 null
来表示没有对象,每次使用引用的时候就必须测试一下引用是否为 null
,这显得有点枯燥,而且势必会产生相当乏味的代码。问题在于 null
没什么自己的行为,只会在你想用它执行任何操作的时候产生 NullPointException
。java.util.Optional
(首次出现是在函数式编程这章)为 null
值提供了一个轻量级代理,Optional
对象可以防止你的代码直接抛出 NullPointException
。
虽然 Optional
是 Java 8 为了支持流式编程才引入的,但其实它是一个通用的工具。为了证明这点,在本节中,我们会把它用在普通的类中。因为涉及一些运行时检测,所以把这一小节放在了本章。
实际上,在所有地方都使用 Optional
是没有意义的,有时候检查一下是不是 null
也挺好的,或者有时我们可以合理地假设不会出现 null
,甚至有时候检查 NullPointException
异常也是可以接受的。Optional
最有用武之地的是在那些“更接近数据”的地方,在问题空间中代表实体的对象上。举个简单的例子,很多系统中都有 Person
类型,代码中有些情况下你可能没有一个实际的 Person
对象(或者可能有,但是你还没用关于那个人的所有信息)。这时,在传统方法下,你会用到一个 null
引用,并且在使用的时候测试它是不是 null
。而现在,我们可以使用 Optional
:
import java.util.*;
class Person {
public final Optional<String> first;
public final Optional<String> last;
public final Optional<String> address;
// etc.
public final Boolean empty;
Person(String first, String last, String address) {
this.first = Optional.ofNullable(first);
this.last = Optional.ofNullable(last);
this.address = Optional.ofNullable(address);
empty = !this.first.isPresent()
&& !this.last.isPresent()
&& !this.address.isPresent();
}
Person(String first, String last) {
this(first, last, null);
}
Person(String last) {
this(null, last, null);
}
Person() {
this(null, null, null);
}
@Override
public String toString() {
if (empty) {
return "<Empty>";
}
return (first.orElse("") +
" " + last.orElse("") +
" " + address.orElse("")).trim();
}
public static void main(String[] args) {
System.out.println(new Person());
System.out.println(new Person("Smith"));
System.out.println(new Person("Bob", "Smith"));
System.out.println(new Person("Bob", "Smith",
"11 Degree Lane, Frostbite Falls, MN"));
}
}
输出结果:
Person
的设计有时候又叫“数据传输对象(DTO,data-transfer object)”。注意,所有字段都是 public
和 final
的,所以没有 getter
和 setter
方法。也就是说,Person
是不可变的,你只能通过构造器给它赋值,之后就只能读而不能修改它的值(字符串本身就是不可变的,因此你无法修改字符串的内容,也无法给它的字段重新赋值)。如果你想修改一个 Person
,你只能用一个新的 Person
对象来替换它。empty
字段在对象创建的时候被赋值,用于快速判断这个 Person
对象是不是空对象。
如果想使用 Person
,就必须使用 Optional
接口才能访问它的 String
字段,这样就不会意外触发 NullPointException
了。
现在假设你已经因你惊人的理念而获得了一大笔风险投资,现在你要招兵买马了,但是在虚位以待时,你可以将 Person Optional
对象放在每个 Position
上:
import java.util.*;
class EmptyTitleException extends RuntimeException {
}
class Position {
private String title;
private Person person;
Position(String jobTitle, Person employee) {
setTitle(jobTitle);
setPerson(employee);
}
Position(String jobTitle) {
this(jobTitle, null);
}
public String getTitle() {
return title;
}
public void setTitle(String newTitle) {
// Throws EmptyTitleException if newTitle is null:
title = Optional.ofNullable(newTitle)
.orElseThrow(EmptyTitleException::new);
}
public Person getPerson() {
return person;
}
public void setPerson(Person newPerson) {
// Uses empty Person if newPerson is null:
person = Optional.ofNullable(newPerson)
.orElse(new Person());
}
@Override
public String toString() {
return "Position: " + title +
", Employee: " + person;
}
public static void main(String[] args) {
System.out.println(new Position("CEO"));
System.out.println(new Position("Programmer",
new Person("Arthur", "Fonzarelli")));
try {
new Position(null);
} catch (Exception e) {
System.out.println("caught " + e);
}
}
}
输出结果:
这里使用 Optional
的方式不太一样。请注意,title
和 person
都是普通字段,不受 Optional
的保护。但是,修改这些字段的唯一途径是调用 setTitle()
和 setPerson()
方法,这两个都借助 Optional
对字段进行了严格的限制。
同时,我们想保证 title
字段永远不会变成 null
值。为此,我们可以自己在 setTitle()
方法里边检查参数 newTitle
的值。但其实还有更好的做法,函数式编程一大优势就是可以让我们重用经过验证的功能(即便是个很小的功能),以减少自己手动编写代码可能产生的一些小错误。所以在这里,我们用 ofNullable()
把 newTitle
转换一个 Optional
(如果传入的值为 null
,ofNullable()
返回的将是 Optional.empty()
)。紧接着我们调用了 orElseThrow()
方法,所以如果 newTitle
的值是 null
,你将会得到一个异常。这里我们并没有把 title
保存成 Optional
,但通过应用 Optional
的功能,我们仍然如愿以偿地对这个字段施加了约束。
EmptyTitleException
是一个 RuntimeException
,因为它意味着程序存在错误。在这个方案里边,你仍然可能会得到一个异常。但不同的是,在错误产生的那一刻(向 setTitle()
传 null
值时)就会抛出异常,而不是发生在其它时刻,需要你通过调试才能发现问题所在。另外,使用 EmptyTitleException
还有助于定位 BUG。
Person
字段的限制又不太一样:如果你把它的值设为 null
,程序会自动把将它赋值成一个空的 Person
对象。先前我们也用过类似的方法把字段转换成 Optional
,但这里我们是在返回结果的时候使用 orElse(new Person())
插入一个空的 Person
对象替代了 null
。
在 Position
里边,我们没有创建一个表示“空”的标志位或者方法,因为 person
字段的 Person
对象为空,就表示这个 Position
是个空缺位置。之后,你可能会发现你必须添加一个显式的表示“空位”的方法,但是正如 YAGNI (You Aren’t Going to Need It,你永远不需要它)所言,在初稿时“实现尽最大可能的简单”,直到程序在某些方面要求你为其添加一些额外的特性,而不是假设这是必要的。
请注意,虽然你清楚你使用了 Optional
,可以免受 NullPointerExceptions
的困扰,但是 Staff
类却对此毫不知情。
import java.util.*;
public class Staff extends ArrayList<Position> {
public void add(String title, Person person) {
add(new Position(title, person));
}
public void add(String... titles) {
for (String title : titles) {
add(new Position(title));
}
}
public Staff(String... titles) {
add(titles);
}
public Boolean positionAvailable(String title) {
for (Position position : this) {
if (position.getTitle().equals(title) &&
position.getPerson().empty) {
return true;
}
}
return false;
}
public void fillPosition(String title, Person hire) {
for (Position position : this) {
if (position.getTitle().equals(title) &&
position.getPerson().empty) {
position.setPerson(hire);
return;
}
}
throw new RuntimeException(
"Position " + title + " not available");
}
public static void main(String[] args) {
Staff staff = new Staff("President", "CTO",
"Marketing Manager", "Product Manager",
"Project Lead", "Software Engineer",
"Software Engineer", "Software Engineer",
"Software Engineer", "Test Engineer",
"Technical Writer");
staff.fillPosition("President",
new Person("Me", "Last", "The Top, Lonely At"));
staff.fillPosition("Project Lead",
new Person("Janet", "Planner", "The Burbs"));
if (staff.positionAvailable("Software Engineer")) {
staff.fillPosition("Software Engineer",
new Person(
"Bob", "Coder", "Bright Light City"));
}
System.out.println(staff);
}
}
输出结果:
注意,在有些地方你可能还是要测试引用是不是 Optional
,这跟检查是否为 null
没什么不同。但是在其它地方(例如本例中的 toString()
转换),你就不必执行额外的测试了,而可以直接假设所有对象都是有效的。
标记接口
有时候使用一个标记接口来表示空值会更方便。标记接口里边什么都没有,你只要把它的名字当做标签来用就可以。
// onjava/Null.java
package onjava;
public interface Null {}
如果你用接口取代具体类,那么就可以使用 DynamicProxy
来自动地创建 Null
对象。假设我们有一个 Robot
接口,它定义了一个名字、一个模型和一个描述 Robot
行为能力的 List<Operation>
:
import java.util.*;
public interface Robot {
String name();
String model();
List<Operation> operations();
static void test(Robot r) {
if (r instanceof Null) {
System.out.println("[Null Robot]");
}
System.out.println("Robot name: " + r.name());
System.out.println("Robot model: " + r.model());
for (Operation operation : r.operations()) {
System.out.println(operation.description.get());
operation.command.run();
}
}
}
你可以通过调用 operations()
来访问 Robot
的服务。Robot
里边还有一个 static
方法来执行测试。
Operation
包含一个描述和一个命令(这用到了命令模式)。它们被定义成函数式接口的引用,所以可以把 lambda 表达式或者方法的引用传给 Operation
的构造器:
import java.util.function.*;
public class Operation {
public final Supplier<String> description;
public final Runnable command;
public Operation(Supplier<String> descr, Runnable cmd) {
description = descr;
command = cmd;
}
}
现在我们可以创建一个扫雪 Robot
:
import java.util.*;
public class SnowRemovalRobot implements Robot {
private String name;
public SnowRemovalRobot(String name) {
this.name = name;
}
@Override
public String name() {
return name;
}
@Override
public String model() {
return "SnowBot Series 11";
}
private List<Operation> ops = Arrays.asList(
new Operation(
() -> name + " can shovel snow",
() -> System.out.println(
name + " shoveling snow")),
new Operation(
() -> name + " can chip ice",
() -> System.out.println(name + " chipping ice")),
new Operation(
() -> name + " can clear the roof",
() -> System.out.println(
name + " clearing roof")));
@Override
public List<Operation> operations() {
return ops;
}
public static void main(String[] args) {
Robot.test(new SnowRemovalRobot("Slusher"));
}
}
输出结果:
假设存在许多不同类型的 Robot
,我们想让每种 Robot
都创建一个 Null
对象来执行一些特殊的操作——在本例中,即提供 Null
对象所代表 Robot
的确切类型信息。这些信息是通过动态代理捕获的:
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.*;
class NullRobotProxyHandler implements InvocationHandler {
private String nullName;
private Robot proxied = new NRobot();
NullRobotProxyHandler(Class<? extends Robot> type) {
nullName = type.getSimpleName() + " NullRobot";
}
private class NRobot implements Null, Robot {
@Override
public String name() {
return nullName;
}
@Override
public String model() {
return nullName;
}
@Override
public List<Operation> operations() {
return Collections.emptyList();
}
}
@Override
public Object
invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return method.invoke(proxied, args);
}
}
public class NullRobot {
public static Robot
newNullRobot(Class<? extends Robot> type) {
return (Robot) Proxy.newProxyInstance(
NullRobot.class.getClassLoader(),
new Class[]{Null.class, Robot.class},
new NullRobotProxyHandler(type));
}
public static void main(String[] args) {
Stream.of(
new SnowRemovalRobot("SnowBee"),
newNullRobot(SnowRemovalRobot.class)
).forEach(Robot::test);
}
}
输出结果:
无论何时,如果你需要一个空 Robot
对象,只需要调用 newNullRobot()
,并传递需要代理的 Robot
的类型。这个代理满足了 Robot
和 Null
接口的需要,并提供了它所代理的类型的确切名字。
Mock 对象和桩
Mock 对象和 **桩(Stub)**在逻辑上都是 Optional
的变体。他们都是最终程序中所使用的“实际”对象的代理。不过,Mock 对象和桩都是假扮成那些可以传递实际信息的实际对象,而不是像 Optional
那样把包含潜在 null
值的对象隐藏。
Mock 对象和桩之间的的差别在于程度不同。Mock 对象往往是轻量级的,且用于自测试。通常,为了处理各种不同的测试场景,我们会创建出很多 Mock 对象。而桩只是返回桩数据,它通常是重量级的,并且经常在多个测试中被复用。桩可以根据它们被调用的方式,通过配置进行修改。因此,桩是一种复杂对象,它可以做很多事情。至于 Mock 对象,如果你要做很多事,通常会创建大量又小又简单的 Mock 对象。