前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 反射最终篇 - Mock 对象和桩

Java 反射最终篇 - Mock 对象和桩

作者头像
JavaEdge
发布2021-02-22 15:47:09
6940
发布2021-02-22 15:47:09
举报
文章被收录于专栏:JavaEdge

Mock 对象和 **桩(Stub)**在逻辑上都是 Optional 的变体。他们都是最终程序中所使用的“实际”对象的代理。 不过,Mock 对象和桩都是假扮成那些可以传递实际信息的实际对象,而不是像 Optional 那样把包含潜在 null 值的对象隐藏。

Mock 对象和桩之间的的差别在于程度不同。

  • Mock 对象往往是轻量级的,且用于自测试。通常,为了处理各种不同的测试场景,我们会创建出很多 Mock 对象。
  • 桩只是返回桩数据,通常是重量级的,在多个测试中被复用。可以根据它们被调用的方式,通过配置进行修改。因此,桩是一种复杂对象,可以做很多事情。 至于 Mock 对象,如果你要做很多事,通常会创建大量又小又简单的 Mock 对象。

接口和类型

interface 关键字的一个重要目标就是允许程序员隔离组件,进而降低耦合度。使用接口可以实现这一目标,但是通过类型信息,这种耦合性还是会传播出去——接口并不是对解耦的一种无懈可击的保障。 比如我们先写一个接口:

实现这个接口

通过使用 RTTI,我们发现 a 是用 B 实现的。通过将其转型为 B,我们可以调用不在 A 中的方法。

这样的操作完全是合情合理的,但是你也许并不想让客户端开发者这么做,因为这给了他们一个机会,使得他们的代码与你的代码的耦合度超过了你的预期。 你可能认为 interface 关键字正在保护你,但其实并没有。另外,在本例中使用 B 来实现 A 这种情况是有公开案例可查的。

一种解决方案是直接声明,如果开发者决定使用实际的类而不是接口,他们需要自己对自己负责。这在很多情况下都是可行的,但“可能”还不够,你或许希望能有一些更严格的控制方式。

最简单的方式是让实现类只具有包访问权限,这样在包外部的客户端就看不到它了:

在包中唯一 public 的部分就是 HiddenC,在被调用时将产生 A接口类型的对象 即使你从 makeA() 返回的是 C 类型,你在包的外部仍旧不能使用 A 之外的任何方法,因为你不能在包的外部命名 C

现在如果你试着将其向下转型为 C,则将被禁止,因为在包的外部没有任何 C 类型可用:

通过使用反射,仍然可以调用所有方法,甚至是 private 方法!如果知道方法名,你就可以在其 0Method 对象上调用 setAccessible(true),就像在 callHiddenMethod() 中看到的那样。

你可能觉得,可以通过只发布编译后的代码来阻止这种情况,但其实这并不能解决问题。因为只需要运行 javap(一个随 JDK 发布的反编译器)即可突破这一限制。 使用 javap

代码语言:javascript
复制
javap -private C

-private 标志表示所有的成员都应该显示,甚至包括私有成员。下面是输出:

代码语言:javascript
复制
class C extends
java.lang.Object implements typeinfo.interfacea.A {
  typeinfo.packageaccess.C();
  public void f();
  public void g();
  void u();
  protected void v();
  private void w();
}

因此,任何人都可以获取你最私有的方法的名字和签名,然后调用它们。

那如果把接口实现为一个私有内部类,又会怎么样呢?下面展示了这种情况:

代码语言:javascript
复制
public C.f()
InnerA$C
public C.g()
package C.u()
protected C.v()
private C.w()

这里对反射仍然没有任何东西可以隐藏。那么如果是匿名类呢?

输出结果:

代码语言:javascript
复制
public C.f()
AnonymousA$1
public C.g()
package C.u()
protected C.v()
private C.w()

看起来任何方式都没法阻止反射调用那些非公共访问权限的方法。对于字段来说也是这样,即便是 private 字段:

输出结果:

代码语言:javascript
复制
i = 1, I'm totally safe, Am I safe?
f.getInt(pf): 1
i = 47, I'm totally safe, Am I safe?
f.get(pf): I'm totally safe
i = 47, I'm totally safe, Am I safe?
f.get(pf): Am I safe?
i = 47, I'm totally safe, No, you're not!

但实际上 final 字段在被修改时是安全的。运行时系统会在不抛出异常的情况下接受任何修改的尝试,但是实际上不会发生任何修改。

通常,所有这些违反访问权限的操作并不是什么十恶不赦的。如果有人使用这样的技术去调用标志为 private 或包访问权限的方法(很明显这些访问权限表示这些人不应该调用它们),那么对他们来说,如果你修改了这些方法的某些地方,他们不应该抱怨。 另一方面,总是在类中留下后门,也许会帮助你解决某些特定类型的问题(这些问题往往除此之外,别无它法)。 总之,不可否认,反射给我们带来了很多好处。

程序员往往对编程语言提供的访问控制过于自信,甚至认为 Java 在安全性上比其它提供了(明显)更宽松的访问控制的语言要优越。然而,正如你所看到的,事实并不是这样。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/06/26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 接口和类型
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档