Premain

Published: by Creative Commons Licence

  • Tags:

代理模式是面向对象中非常常用的一种设计模式。但是当我们需要实现类似与切面时,仅仅使用代理是不足够的。因为代理模式的是面向接口的,但是我们并不能保证抽象出一致的接口并使用统一的代理,这就导致我们必须为每个接口实现一个对应的代理,这样代价就过于高昂了。

Spring中提供了面向切面编程,我们可以通过提供Advice,而Spring根据Advice为你自动生成代理。我们将所有的Bean的生命周期都交由了Spring来进行管理,正是因为如此,当我们向Spring请求Bean或者Spring执行自动注入的时候,能够生成对应的代理类,并给与我们代理类的对象,而正是由于我们对于Spring内部机制的一无所知,我们才能彻底与切面的逻辑解耦,从而专注于业务代码的实现。

但是不是所有人都有Spring的能力,能让他人遵循自己框架的规范进行编程(假如我们不从Spring取Bean,而是直接new,那么我们也将无法享受到AOP)。

但是JVM为我们提供了JVMTI(Java Virtual Machine Tool Interface),一套为JVM相关工具提供的本地编程接口集合。JVMTI从JAVA SE 5开始引入,取代了之前使用的JVMPI(Java Virtual Machine Profile Interface)和JVMDI(Java Virtual Machine Debug Interface)。

当我们在控制台输入java -?时,控制台将会输出下面信息:

-javaagent:<jarpath>[=<选项>]
                  加载 Java 编程语言代理, 请参阅 java.lang.instrument

该选项表明我们能为我们启动的jvm添加一个代理库。代理库是一个jar包,其中的META-INF/MANIFEST.MF文件包含下面一行内容:

Agent-Class: YourAgentClass

Agent-Class指定了jar包中代理类入口。除此之外我们必须在代理类入口中提供和main方法类似的另外两个方法之一:

public static void premain(String[] args, Instrumentation inst)
public static void premain(String[] args)

当两个方法同时出现时,前者将被选择作为入口。

容易发现premain和main的最大区别是premain还可以接受一个Instrumentation的实例作为额外参数。Instrumentation位于java.lang.instrument包下,该类允许我们添加类转换器重定义类重转换类获取所有已加载类获取已初始化类等强大功能。

代理jar包中的premain方法将会在main方法调用之前调用。我们可以在premain中为Instrumentation增加转换器,这样之后每当有新类加载,旧类重定义时都会回调我们的转换器。下面给一个例子:

public static void premain(String agentArgs, Instrumentation inst) {
	System.out.println("Register agent");
	inst.addTransformer(new ClassFileTransformer() {
		@Override
		public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classFileBuffer) throws IllegalClassFormatException {
			System.out.println("loading " + className);
			return null;
		}
	});
}

其中main方法非常简单,如下:

public static void main(String[] args){
    System.out.println("Hello, World!");
}

接下来我们在启动jvm时添加-javaagent参数后,控制台输出下面内容:

Register agent
loading java/lang/invoke/MethodHandleImpl
loading java/lang/invoke/MethodHandleImpl$1
loading java/lang/invoke/MethodHandleImpl$2
loading java/util/function/Function
loading java/lang/invoke/MethodHandleImpl$3
loading java/lang/invoke/MethodHandleImpl$4
loading java/lang/ClassValue
loading java/lang/ClassValue$Entry
loading java/lang/ClassValue$Identity
loading java/lang/ClassValue$Version
loading java/lang/invoke/MemberName$Factory
loading java/lang/invoke/MethodHandleStatics
loading java/lang/invoke/MethodHandleStatics$1
loading sun/misc/PostVMInitHook
loading sun/usagetracker/UsageTrackerClient
loading java/util/concurrent/atomic/AtomicBoolean
loading sun/usagetracker/UsageTrackerClient$1
loading sun/usagetracker/UsageTrackerClient$4
loading sun/usagetracker/UsageTrackerClient$2
loading jdk/internal/util/EnvUtils
loading sun/usagetracker/UsageTrackerClient$3
loading java/io/FileOutputStream$1
loading sun/launcher/LauncherHelper
loading java/util/concurrent/ConcurrentHashMap$ForwardingNode
loading cn/dalt/test/asm/SimpleMain
loading sun/launcher/LauncherHelper$FXHelper
loading java/lang/Class$MethodArray
loading java/lang/Void
Hello, World!
loading java/lang/Shutdown
loading java/lang/Shutdown$Lock

这里仅仅是非常简单的打印日志信息,而并没有做实际的转换操作。如果我们引入asm技术,并将修改后的类作为结果返回,就能轻松实现我们之前提到的AOP,而这个AOP是完全的,不需要我们管理类的生命周期,甚至我们自己new的对象也能被完全掌控。


参考资料