用过Spring的人都能感受到AOP带来的好处。什么是AOP(Aspect Oriented Programming)呢?所谓AOP就是面向切面编程。将业务过程中具有共性的功能提取出来,统一进行处理,这样在简化业务代码的同时,方便管理。一旦需求改变,只需要改变提取出的切面即可,无需到处修改业务代码中的功能。
而java中的动态代理就可以实现简单的AOP。下面的例子中通过动态代理为方法添加日志,用于记录进入方法和退出方法。而这个功能在Spring的AOP中是一个十分常见的应用例子。
首先定义接口HelloWorld,
public interface HelloWorld {
public void sayHello();
public void sayHello1();
}
然后编写业务代码也就是上面接口的实现类,
public class HelloWorldImpl implements HelloWorld{
@Override
public void sayHello() {
// TODO Auto-generated method stub
System.out.println("helloworld");
}
@Override
public void sayHello1() {
// TODO Auto-generated method stub
System.out.println("helloworld1");
}
}
实现代理类,这里需要仔细区分 代理对象,被代理对象,被代理之后的对象。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyProxy implements InvocationHandler{
private Object delegate;
// 设置被代理的对象
public void setDelegate(Object obj){
this.delegate = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
System.out.println("Enter function : " + method.getName());
method.invoke(delegate, args);
System.out.println("Leave function : " + method.getName());
return null;
}
public static void main(String[] args) {
// 生成被代理对象
HelloWorld helloworld = new HelloWorldImpl();
// 生成代理对象
MyProxy proxy = new MyProxy();
proxy.setDelegate(helloworld);
// 生成被代理后的对象
HelloWorld helloworldProxy = (HelloWorld) Proxy.newProxyInstance(helloworld.getClass().getClassLoader(), helloworld.getClass().getInterfaces(), proxy);
helloworldProxy.sayHello();
helloworldProxy.sayHello1();
// 查看新生成的类的名称
System.out.println(helloworldProxy.getClass().getName());
// 查看新生成的类实现的接口
Class[] interfaces = helloworldProxy.getClass().getInterfaces();
for (Class clazz : interfaces){
System.out.println(clazz.getName());
}
}
}
我们可以看到上面的类MyProxy实现了接口InvocationHandler,这个接口中有invoke方法。通过Proxy的静态方法newProxyInstance生成被代理之后的对象,这里传入的参数分别是被代理对象的类加载器,被代理对象的接口,以及代理对象。在上面的例子中这个代理对象就是MyProxy的实例proxy。在newProxyInstance方法中会生成一个新的类,并且这个新类的类加载器就是传入的类加载器。并且这个新类实现了传入的接口,因此在上面的例子中代理之后的对象可以强制转换成HelloWorld类型。
运行上面的代码后,看到如下输出
Enter function : sayHello
helloworld
Leave function : sayHello
Enter function : sayHello1
helloworld1
Leave function : sayHello1
$Proxy0
HelloWorld
可以看到通过代理类,被代理类的方法被调用的时候都会打印出相应的信息。这是为什么呢?因为生成的代理之后的对象在调用相应的方法时,会调用代理对象的invoke方法,而在我们的实现中invoke方法主要干了以下几件事情
1)首先会打印出“Ener function : xxx”字样
2)然后通过反射机制调用被代理对象相应的方法,这个方法外层的方法具有相同的名字
3)之后会打印出“Leave function: xxx”字样
而之后打印出的$Proxy0就是新生成的类的名字,HelloWorld就是$Proxy0实现了的接口。
需要注意的是,要将被代理对象传入到代理对象中,然后在代理的invoke方法中调用被代理对象的方法。千万不要再invoke方法中使用如下语句,
method.invoke(proxy, args)。
假设调用了helloworldProxy.sayHello(),然后会进入到methond.invoke(proxy, args),由于这里传入的proxy就是helloworldProxy,所以会再次进入helloworldProxy.sayHello()方法,这就形成了死递归。
为了验证上面的猜想,可以看一下newProxyInstance方法,新生成的类主要通过如下代码生成,
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
try {
proxyClass = defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
ProxyGenerator位于sun.misc包中,可以看一下openjdk中是如何实现的。
generateProxyClass方法如下,
public static byte[] generateProxyClass(final String name,
Class[] interfaces)
{
ProxyGenerator gen = new ProxyGenerator(name, interfaces);
final byte[] classFile = gen.generateClassFile();
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
try {
FileOutputStream file =
new FileOutputStream(dotToSlash(name) + ".class");
file.write(classFile);
file.close();
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
return classFile;
}
字节码的生成主要通过gen的generateClassFile方法生成。
private byte[] generateClassFile() {
/* ============================================================
* Step 1: Assemble ProxyMethod objects for all methods to
* generate proxy dispatching code for.
*/
/*
* Record that proxy methods are needed for the hashCode, equals,
* and toString methods of java.lang.Object. This is done before
* the methods from the proxy interfaces so that the methods from
* java.lang.Object take precedence over duplicate methods in the
* proxy interfaces.
*/
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
/*
* Now record all of the methods from the proxy interfaces, giving
* earlier interfaces precedence over later ones with duplicate
* methods.
*/
for (int i = 0; i < interfaces.length; i++) {
Method[] methods = interfaces[i].getMethods();
for (int j = 0; j < methods.length; j++) {
addProxyMethod(methods[j], interfaces[i]);
}
}
/*
* For each set of proxy methods with the same signature,
* verify that the methods' return types are compatible.
*/
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
checkReturnTypes(sigmethods);
}
/* ============================================================
* Step 2: Assemble FieldInfo and MethodInfo structs for all of
* fields and methods in the class we are generating.
*/
try {
methods.add(generateConstructor());
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyMethod pm : sigmethods) {
// add static field for method's Method object
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;",
ACC_PRIVATE | ACC_STATIC));
// generate code for proxy method and add it
methods.add(pm.generateMethod());
}
}
methods.add(generateStaticInitializer());
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception");
}
if (methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
}
if (fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
}
/* ============================================================
* Step 3: Write the final class file.
*/
/*
* Make sure that constant pool indexes are reserved for the
* following items before starting to write the final class file.
*/
cp.getClass(dotToSlash(className));
cp.getClass(superclassName);
for (int i = 0; i < interfaces.length; i++) {
cp.getClass(dotToSlash(interfaces[i].getName()));
}
/*
* Disallow new constant pool additions beyond this point, since
* we are about to write the final constant pool table.
*/
cp.setReadOnly();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
try {
/*
* Write all the items of the "ClassFile" structure.
* See JVMS section 4.1.
*/
// u4 magic;
dout.writeInt(0xCAFEBABE);
// u2 minor_version;
dout.writeShort(CLASSFILE_MINOR_VERSION);
// u2 major_version;
dout.writeShort(CLASSFILE_MAJOR_VERSION);
cp.write(dout); // (write constant pool)
// u2 access_flags;
dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
// u2 this_class;
dout.writeShort(cp.getClass(dotToSlash(className)));
// u2 super_class;
dout.writeShort(cp.getClass(superclassName));
// u2 interfaces_count;
dout.writeShort(interfaces.length);
// u2 interfaces[interfaces_count];
for (int i = 0; i < interfaces.length; i++) {
dout.writeShort(cp.getClass(
dotToSlash(interfaces[i].getName())));
}
// u2 fields_count;
dout.writeShort(fields.size());
// field_info fields[fields_count];
for (FieldInfo f : fields) {
f.write(dout);
}
// u2 methods_count;
dout.writeShort(methods.size());
// method_info methods[methods_count];
for (MethodInfo m : methods) {
m.write(dout);
}
// u2 attributes_count;
dout.writeShort(0); // (no ClassFile attributes for proxy classes)
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception");
}
return bout.toByteArray();
}
代理方法的产生主要通过如下的代码,
for (int i = 0; i < interfaces.length; i++) {
Method[] methods = interfaces[i].getMethods();
for (int j = 0; j < methods.length; j++) {
addProxyMethod(methods[j], interfaces[i]);
}
}
继续看addProxyMethod是如何工作的,
private void addProxyMethod(Method m, Class fromClass) {
String name = m.getName();
Class[] parameterTypes = m.getParameterTypes();
Class returnType = m.getReturnType();
Class[] exceptionTypes = m.getExceptionTypes();
String sig = name + getParameterDescriptors(parameterTypes);
List<ProxyMethod> sigmethods = proxyMethods.get(sig);
if (sigmethods != null) {
for (ProxyMethod pm : sigmethods) {
if (returnType == pm.returnType) {
/*
* Found a match: reduce exception types to the
* greatest set of exceptions that can thrown
* compatibly with the throws clauses of both
* overridden methods.
*/
List<Class<?>> legalExceptions = new ArrayList<Class<?>>();
collectCompatibleTypes(
exceptionTypes, pm.exceptionTypes, legalExceptions);
collectCompatibleTypes(
pm.exceptionTypes, exceptionTypes, legalExceptions);
pm.exceptionTypes = new Class[legalExceptions.size()];
pm.exceptionTypes =
legalExceptions.toArray(pm.exceptionTypes);
return;
}
}
} else {
sigmethods = new ArrayList<ProxyMethod>(3);
proxyMethods.put(sig, sigmethods);
}
sigmethods.add(new ProxyMethod(name, parameterTypes, returnType,
exceptionTypes, fromClass));
}
继续进入到ProxyMethod,然后可以在generateMethod中看到
out.writeByte(opc_invokeinterface);
out.writeShort(cp.getInterfaceMethodRef(
"java/lang/reflect/InvocationHandler",
"invoke",
"(Ljava/lang/Object;Ljava/lang/reflect/Method;" +
"[Ljava/lang/Object;)Ljava/lang/Object;"));
out.writeByte(4);
out.writeByte(0);
写入了字节码opc_invokeinterface,查看这个常量值,
private static final int opc_invokeinterface = 185;
在虚拟机规范中查看这个字节码,
总和上面可以看出代码主要负责调用invoke方法,而这个invoke方法属于某个invokeHandler接口的实例。