Java中对已经有的类进行修改,改变或调整其执行,这可以通过代理来实现。Java的class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码。JVM虚拟机读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息,生成对应的 Class对象。如果对class文件进行了修改,就可以改变程序。
先给一个简单的示例,演示加载class的二进制文件,得到class的实例对象,然后调用。
package samples1;
// 定义一个简单的类
public class SimpleCode {
public void code()
{
System.out.println("--SimpleCode's code");
}
}
package samples1;
/**
* 自定义一个类加载器,用于将字节码转换为class对象
*/
public class SimpleClassLoader extends ClassLoader {
public Class> defineMyClass( byte[] b, int off, int len)
{
//return super.defineClass(b, off, len);
return super.defineClass(null, b, off, len,null);
}
}
//
package samples1;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class MyTest
{
public static void main(String[] args) throws IOException
{
//读取本地的class文件内的字节码,转换成字节码数组
File file = new File(".");
InputStream input = new FileInputStream(file.getCanonicalPath()+"/classes/samples1/SimpleCode.class");
byte[] result = new byte[1024];
int count = input.read(result);
// 使用自定义的类加载器将 byte字节码数组转换为对应的class对象
SimpleClassLoader loader = new SimpleClassLoader();
Class clazz = loader.defineMyClass( result, 0, count);
//测试加载是否成功,打印class 对象的名称
System.out.println(clazz.getCanonicalName());
//实例化一个Programmer对象
try
{
Object o= clazz.newInstance();
//调用SimpleCode的code方法
clazz.getMethod("code", null).invoke(o, null);
}
catch (IllegalArgumentException
| NoSuchMethodException | SecurityException e)
{
e.printStackTrace();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
编译执行:
$ javac -cp classes -d classes *.java
$
$ java -cp classes samples1.MyTest
class Name: samples1.SimpleCode
--SimpleCode's code
$
从上面的示例中我们可以看到,通过class文件,我们可以自己加载并生成一个类实例,并进行调用。
如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力了。
静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
动态代理是实现JDK里的InvocationHandler接口的invoke方法,但注意的是代理的是接口,也就是你的业务类必须要实现接口,通过Proxy里的newProxyInstance得到代理对象。
实现动态代理有几个第三方的库,提供了方便的代理实现。
还有一种动态代理CGLIB,代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行时,动态修改字节码达到修改类的目的。
定义了一个HelloService接口,有2个实现,HelloServiceProxy、HelloServiceImpl,这两个类都实现了HelloService接口。其中HelloServiceImpl类是HelloService接口的真正实现者,是一个委托类,而HelloServiceProxy类是通过调用HelloServiceImpl类的相关方法来提供特定服务的,是一个代理类。
HelloServiceProxy类的echo()方法和getTime()方法会分别调用被代理的HelloServiceImpl对象的echo()方法和getTime()方法,并且在方法调用前后都会执行一些操作,比如简单的记录一些信息。由此可见,代理类可以为委托类预处理消息、把消息转发给委托类和事后处理消息等。
package proxy;
import java.util.Date;
public interface HelloService{
public String echo(String msg);
public Date getTime();
}
package proxy;
import java.util.Date;
public class HelloServiceImpl implements HelloService{
public String greet(String name){
return "Hi, "+name;
}
public Date getTime(){
return new Date();
}
}
package proxy;
import java.util.Date;
public class HelloServiceProxy implements HelloService{
//表示被代理的HelloService 实例
private HelloService helloService;
public HelloServiceProxy(HelloService helloService){
this.helloService=helloService;
}
public void setHelloServiceProxy(HelloService helloService){
this.helloService=helloService;
}
public String greet(String name){
//预处理
System.out.println("before calling greet()");
//调用被代理的HelloService 实例的greet()方法
String result=helloService.greet(name);
//事后处理
System.out.println("after calling greet()");
return result;
}
public Date getTime(){
//预处理
System.out.println("before calling getTime()");
//调用被代理的HelloService 实例的getTime()方法
Date date=helloService.getTime();
//事后处理
System.out.println("after calling getTime()");
return date;
}
}
使用的测试类
package proxy;
public class ProxyTest{
public static void main(String args[]){
HelloService helloService=new HelloServiceImpl();
HelloService helloServiceProxy=new HelloServiceProxy(helloService);
System.out.println(helloServiceProxy.greet("David"));
System.out.println(helloServiceProxy.getTime());
}
}
编译并执行,查看结果。
$ javac -d classes -cp classes *.java
$ java -cp classes proxy.ProxyTest
before calling greet()
after calling greet()
Hi, David
before calling getTime()
after calling getTime()
Mon Oct 16 15:47:09 CST 2017
$
总结: 静态代理要使用的class在使用前都存在,在代理类中知道并可以使用,这种代理类就是静态代理类。
和静态代理类不同,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java反射机制可以生成任意类型的动态代理类。
java.lang.reflect
包中的Proxy
类和InvocationHandler
接口提供了生成动态代理类的能力。
Proxy类提供了创建动态代理类及其实例的静态方法。包括:
(1)getProxyClass()静态方法负责创建动态代理类,它的完整定义如下:
public static Class
InvocationHandler handler = new MyInvocationHandler(...);
Class> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).
newInstance(handler);
或者使用简化的方法:
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class>[] { Foo.class },
handler);
JavaDoc中介绍了一个代理类有一些特点:
由Proxy类的静态方法创建的动态代理类具有以下特点:
- 动态代理类是public、final和非抽象类型,如果其代理的接口都是public的;如果有接口是non-public, 则代理类也是non-public.
- 动态代理类继承了java.lang.reflect.Proxy类;
- 动态代理类的名字以“$Proxy”开头;
- … …
由Proxy类的静态方法创建的动态代理类的实例具有以下特点:
1. 假定变量foo 是一个动态代理类的实例,并且这个动态代理类实现了Foo 接口,那么foo instanceof Foo
的值为true。把变量foo强制转换为Foo类型是合法的:(Foo) foo //合法
。
每个动态代理类实例都和一个`InvocationHandler
实例关联。Proxy类的getInvocationHandler(Objectproxy)
静态方法返回与参数proxy指定的代理类实例所关联的InvocationHandler
对象。
假定Foo接口有一个amethod()
方法,那么当程序调用动态代理类实例foo的amethod()
方法时,该方法会调用与它关联的InvocationHandler
对象的invoke()
方法。
InvocationHandler 接口为方法调用接口,它声明了负责调用任意一个方法的invoke()方法.
Object invoke(Object proxy,Method method,Object[] args) throwsThrowable
参数proxy指定动态代理类实例,参数method指定被调用的方法,参数args指定向被调用方法传递的参数,invoke()方法的返回值表示被调用方法的返回值。
下面看一个示例:
package DynamicProxy;
/**
* 抽象接口
*/
public interface Subject {
public void greet();
}
package DynamicProxy;
public class RealSubject implements Subject{
@Override
public void greet() {
//
System.out.println("greet By---"+getClass());
}
}
最重要的是:建立InvocationHandler用来响应代理的任何调用。
package DynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyHandler implements InvocationHandler {
private Object proxied;
public ProxyHandler( Object proxied )
{
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("prepare, at proxy!");
//转调具体目标对象的方法
Object object= method.invoke(proxied, args);
System.out.println("finished, at proxy!");
return object;
}
}
测试类:
package DynamicProxy;
import java.lang.reflect.Proxy;
public class DynamicProxyTest {
public static void main( String args[] )
{
RealSubject realImpl = new RealSubject();
Subject proxySubject = (Subject)Proxy.newProxyInstance(
Subject.class.getClassLoader(),
new Class[]{Subject.class},
new ProxyHandler(realImpl) );
proxySubject.greet();;
}
}
执行,检查结果:
$ mkdir classes
$ javac -d classes *.java
$ java -cp classes DynamicProxy.DynamicProxyTest
prepare, at proxy!
greet By---class DynamicProxy.RealSubject
finished, at proxy!
$
public static Class> getProxyClass(ClassLoader loader,
Class>... interfaces)
throws IllegalArgumentException
{
final Class>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
return getProxyClass0(loader, intfs);
}
private static void checkProxyAccess(Class> caller,
ClassLoader loader,
Class>... interfaces)
{
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader ccl = caller.getClassLoader();
if (VM.isSystemDomainLoader(loader) && !VM.isSystemDomainLoader(ccl)) {
sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
ReflectUtil.checkProxyPackageAccess(ccl, interfaces);
}
}
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*/
private static Class> getProxyClass0(ClassLoader loader,
Class>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
public static Object newProxyInstance(ClassLoader loader,
Class>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
在测试类main方法中,添加System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
,将会把生成的代理类保留在磁盘上,文件如此:com/sun/proxy/$Proxy0.class
,可以反编译查看。
动态代理使用InvocationHandler的作用:
在静态代理中,代理Proxy中的方法,都指定了调用了特定的realSubject中的对应的方法:
在静态代理模式下,Proxy所做的事情,就是调用Subject接口的方法时,调用realSubject对应的方法;
动态代理工作的基本模式就是将自己的方法功能的实现交给 InvocationHandler角色,外界对Proxy角色中的每一个方法的调用,Proxy角色都会交给InvocationHandler来处理,而InvocationHandler则调用具体对象角色的方法。
代理Proxy和RealSubject应该实现相同的API,在面向对象的编程之中,如果我们想要约定Proxy 和RealSubject可以实现相同的功能,有两种方式:
- a. 一个比较直观的方式,就是定义一个功能接口,然后让Proxy 和RealSubject来实现这个接口。JDK使用此思路实现代理。
b. 还有比较隐晦的方式,就是通过继承。因为如果Proxy 继承自RealSubject,这样Proxy则拥有了RealSubject的功能,Proxy还可以通过重写RealSubject中的方法,来实现多态。而cglib 则是以此思路设计的。
JDK中提供的生成动态代理类的机制有个鲜明的特点是: 某个类必须有实现的接口,而生成的代理类也只能代理某个类接口定义的方法。极端的情况是:如果某个类没有实现接口,那么这个类就不能同JDK产生动态代理了!
而cglib–“CGLIB(Code Generation Library),是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。”
cglib 创建某个类A的动态代理类的模式是:
- 1. 查找A上的所有非final 的public类型的方法定义;
- 2. 将这些方法的定义转换成字节码;
- 3. 将组成的字节码转换成相应的代理的class对象;
- 4. 实现 MethodInterceptor接口,用来处理 对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)
网友有个有趣的例子:定义一个Programmer类,一个Hacker类
package samples;
public class Programmer {
public void code()
{
System.out.println("I'm a Programmer,Just Coding.....");
}
}
package samples;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/*
* 实现了方法拦截器接口
*/
public class Hacker implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("**** I am a hacker,Let's see what the poor programmer is doing Now...");
proxy.invokeSuper(obj, args);
System.out.println("**** Oh,what a poor programmer.....");
return null;
}
}
package samples;
import net.sf.cglib.proxy.Enhancer;
public class Test {
public static void main(String[] args) {
Programmer progammer = new Programmer();
Hacker hacker = new Hacker();
//cglib 中加强器,用来创建动态代理
Enhancer enhancer = new Enhancer();
//设置要创建动态代理的类
enhancer.setSuperclass(progammer.getClass());
// 设置回调,这里相当于是对于代理类上所有方法的调用,都会调用CallBack,
// 而Callback则需要实行intercept()方法进行拦截
enhancer.setCallback(hacker);
Programmer proxy =(Programmer)enhancer.create();
proxy.code();
}
}
执行
**** I am a hacker,Let's see what the poor programmer is doing Now...
I'm a Programmer,Just Coding.....
**** Oh,what a poor programmer.....
在cglib中一些关键的类的列表:
- java.lang.reflect.Method;
- net.sf.cglib.core.ReflectUtils;
- net.sf.cglib.core.Signature;
- net.sf.cglib.proxy.Callback;
- net.sf.cglib.proxy.Factory;
- net.sf.cglib.proxy.MethodInterceptor;
- net.sf.cglib.proxy.MethodProxy;
Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba所创建的。
它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
下面展示一个简单的使用示例:
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
public class MyGenerator {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
//创建Programmer类
CtClass cc= pool.makeClass("com.samples.Programmer");
//定义code方法
CtMethod method = CtNewMethod.make("public void code(){}", cc);
//插入方法代码
method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
cc.addMethod(method);
//保存生成的字节码
cc.writeFile("d://temp");
}
}
ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
不过ASM在创建class字节码的过程中,操纵的级别是底层JVM的汇编指令级别,这要求ASM使用者要对class组织结构和JVM汇编指令有一定的了解。
比如,有下面的programmer类:
package com.samples;
import java.io.PrintStream;
public class Programmer {
public void code()
{
System.out.println("I'm a Programmer,Just Coding.....");
}
}
当没有此Programmer类源码,而使用ASM来生成,可以这么实现.
使用ASM框架提供了ClassWriter 接口,通过访问者模式进行动态创建class字节码,
package samples;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class MyGenerator {
public static void main(String[] args) throws IOException {
System.out.println();
ClassWriter classWriter = new ClassWriter(0);
// 通过visit方法确定类的头部信息
classWriter.visit(Opcodes.V1_7,// java版本
Opcodes.ACC_PUBLIC,// 类修饰符
"Programmer", // 类的全限定名
null, "java/lang/Object", null);
//创建构造函数
MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "" , "()V", null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "" ,"()V");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
// 定义code方法
MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "code", "()V",
null, null);
methodVisitor.visitCode();
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
methodVisitor.visitLdcInsn("I'm a Programmer,Just Coding.....");
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",
"(Ljava/lang/String;)V");
methodVisitor.visitInsn(Opcodes.RETURN);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
classWriter.visitEnd();
// 使classWriter类已经完成
// 将classWriter转换成字节数组写到文件里面去
byte[] data = classWriter.toByteArray();
File file = new File("Programmer.class");
FileOutputStream fout = new FileOutputStream(file);
fout.write(data);
fout.close();
}
}