系列文章:
不一样的视角来学习Spring源码之容器与Bean—上
不一样的视角来学习Spring源码之容器与Bean—下
AOP 底层实现方式之一是代理,由代理结合通知和目标,提供增强功能
除此以外,aspectj 提供了两种另外的 AOP 底层实现:
简单比较的话:
准备一个待增强的MyService类
@Service
public class MyService {
private static final Logger log = LoggerFactory.getLogger(MyService.class);
public static void foo() {
log.debug("foo()");
}
}
准备一个切面类:
@Component
@Aspect
public class MyAspect {
private static final Logger log = LoggerFactory.getLogger(MyAspect.class);
@Before("execution(* com.aop01.service.MyService.foo())")
public void before() {
log.debug("before()");
}
}
引入aspectJ的编译插件acj编译器
<build>
<plugins>
<!-- 编译期织入 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.14.0</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<configuration>
<skip>false</skip>
</configuration>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
运行程序,查看被acj编译器动态织入后的class文件:
AspectJ官网
更多AspectJ的内容可以参考本篇文章进行学习
Java Agent是JVM级别的AOP实现:
相关技术:
javassist基础以及如何使用javassist实现AOP
使用演示:
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
</dependency>
我们这里通过agent从jvm层面完成对指定方法执行时间的计算功能
public class MyAgent {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("premain-1." + agentArgs);
inst.addTransformer(new MyMonitor());
}
}
转换器,计算所有类文件在被classLoader加载器,都会回调注册的转换器—inst.addTransformer(new MyMonitor()),可以通过转换器的transform改写类的定义或者给类新增某些定义
/**
* <p>
* 增加计算方法执行时间的横切逻辑
* </p>
* @author 大忽悠
* @create 2022/3/28 13:36
*/
public class MyMonitor implements ClassFileTransformer {
private List<String> getTargetMethods()
{
return Arrays.asList("sayHello1","sayHello2");
}
/**
* 从环境变量中读取出指定的类名
*/
private String getTargetClassName()
{
return "com.agent.TestBean";
}
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
String targetClassName = getTargetClassName();
//默认传入的形式是java/util/list
className = className.replace("/", ".");
if(className.equals(targetClassName))
{
System.out.println("==========================>拦截的指定类名为: "+className);
try {
//用于取得字节码类,必须在当前classpath中,使用全称
CtClass ctClass = ClassPool.getDefault().get(className);
//遍历需要增强的方法列表
List<String> targetMethods = getTargetMethods();
for (int i = 0; i < targetMethods.size(); i++) {
//获取方法名
String methodName=targetMethods.get(i);
System.out.println("==========================>当前被增强的方法名为: "+methodName);
//准备方法体
String outputStr ="\nSystem.out.println" +
"(\"this method "+methodName+" cost:\" +(endTime - startTime) +\"ms.\");";
//得到方法对象实例
CtMethod ctMethod = ctClass.getDeclaredMethod(methodName);
//构建新的方法体
StringBuilder bodyStr =new StringBuilder();
bodyStr.append("{");
bodyStr.append("long startTime = System.currentTimeMillis();");
//调用原有代码,类似于method();($$)表示所有的参数
CtMethod newMethod = CtNewMethod.copy(ctMethod, methodName+"$Proxy", ctClass,null);
//在新的方法里面调用旧方法---不能在就方法里面调用旧方法,那样子就无限递归了
bodyStr.append(methodName+"();\n");
bodyStr.append("long endTime = System.currentTimeMillis();");
bodyStr.append(outputStr);
bodyStr.append("}");
//更换方法体
newMethod.setBody(bodyStr.toString());
//增加新的方法
ctClass.addMethod(newMethod);
}
return ctClass.toBytecode();
} catch (NotFoundException e) {
e.printStackTrace();
} catch (CannotCompileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
MANIFEST.MF文件
Manifest-Version: 1.0
Premain-Class: com.dhy.MyAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
将这个agent打成jar包
待增强类:
public void sayHello1()
{
try {
Thread.sleep(2000);
System.out.println("你好呀!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void sayHello2()
{
try {
Thread.sleep(2000);
System.out.println("hhh!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试主类:
public class AgentTest {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
TestBean testBean=new TestBean();
Method[] declaredMethods = testBean.getClass().getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if(declaredMethod.getName().equals("sayHello1$Proxy")||declaredMethod.getName().equals("sayHello2$Proxy"))
{
declaredMethod.invoke(testBean,null);
}
}
}
}
还需要在IDEA增加VM参数---》指定agnet的jar包位置: -javaagent:C:\Users\zdh\IdeaProjects\SpringSourceCode\target\myAgnet-1.0-SNAPSHOT.jar
完美!
java文件是先被编译成class二进制字节码文件,再加载进虚拟机的,Aspectj是在没加载前,直接修改生成的class文件完成aop逻辑织入的,但是这里的agent是通过加载前的回调处理接口,对class文件进行aop逻辑切入的,所以我们查看生成的class文件,会发现并没有变化
我们可以使用Arthas在运行期间,对程序中某个类进行反编译,查看反编译之后生成的class字节码文件,下面我们来尝试一下
public class AgentTest {
public static void main(String[] args) {
TestBean testBean=new TestBean();
Method[] declaredMethods = testBean.getClass().getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if(declaredMethod.getName().equals("sayHello1$Proxy")||declaredMethod.getName().equals("sayHello2$Proxy"))
{
declaredMethod.invoke(testBean,null);
}
}
//阻塞,方便通过arthas动态观察
System.in.read();
}
}
启动arthas
public class JdkProxyDemo {
interface Foo {
void foo();
}
static class Target implements Foo {
@Override
public void foo() {
System.out.println("target foo");
}
}
public static void main(String[] param) {
// 目标对象
Target target = new Target();
// 代理对象
Foo proxy = (Foo) Proxy.newProxyInstance(
Target.class.getClassLoader(), new Class[]{Foo.class},
(p, method, args) -> {
System.out.println("proxy before...");
Object result = method.invoke(target, args);
System.out.println("proxy after...");
return result;
});
// 调用代理
proxy.foo();
}
}
public class CglibProxyDemo {
static class Target {
public void foo() {
System.out.println("target foo");
}
}
public static void main(String[] param) {
// 目标对象
Target target = new Target();
// 代理对象
Target proxy = (Target) Enhancer.create(Target.class,
(MethodInterceptor) (p, method, args, methodProxy) -> {
System.out.println("proxy before...");
//不会使用反射
Object result = methodProxy.invoke(target, args);
// 另一种调用方法,不需要目标对象实例
// Object result = methodProxy.invokeSuper(p, args);
System.out.println("proxy after...");
return result;
});
// 调用代理
proxy.foo();
}
}
运行结果与 jdk 动态代理相同
被代理类实现的接口:
public interface Target {
String sayHello();
}
被代理类:
public class RealTarget implements Target{
@Override
public String sayHello() {
System.out.println("hello world");
return "hello";
}
}
代理类呢? 先来模拟一下最简单的实现:
public class $Proxy0 implements Target{
private Target target;
public $Proxy0(Target target) {
this.target = target;
}
@Override
public String sayHello() {
System.out.println("========>前置切入");
String hello = target.sayHello();
System.out.println("后置切入<==========");
return hello;
}
}
代理类中肯定需要持有被代理类的实例,这样才能调用其方法,所以正常第一反应应该写成上面这样
测试:
public class MockJdk {
public static void main(String[] args) {
Target target=new RealTarget();
$Proxy0 $Proxy0=new $Proxy0(target);
$Proxy0.sayHello();
}
}
问题: 代理类里面的横切逻辑不应该是写死的,应该由用户写好,然后进行织入,但是上面的写法显然是不对的,所以需要优化,这时候就要引入一个中间类,来承担切入逻辑的功能:
联系上面的jdk的动态代理例子,大家应该猜到这个中间类是谁了----》InvocationHandler
我们这边就直接利用jdk提供的这个InvocationHandler接口即可
public interface InvocationHandler {
public Object invoke(
//代理对象 当前正在执行的方法 方法参数
Object proxy, Method method, Object[] args)
throws Throwable;
}
我们改进一下$Proxy0代理类
/**
* @author 大忽悠
* @create 2022/3/29 9:39
*/
public class $Proxy0 implements Target{
private InvocationHandler h;
private static Method m1;
static {
try {
m1=Target.class.getMethod("sayHello");
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public $Proxy0(InvocationHandler h) {
this.h=h;
}
@Override
public String sayHello() {}
Object ret = null;
try {
//这里将方法具体需要被织入什么样的逻辑交给用户决定
ret = h.invoke(this,m1,null);
}
catch (RuntimeException | Error e)
{
//运行时异常直接抛出
throw e;
}
catch (Throwable e) {
//转换成运行时异常抛出
throw new UndeclaredThrowableException(e);
}
return (String) ret;
}
}
测试类:
public class MockJdk {
public static void main(String[] args) {
Target target=new RealTarget();
$Proxy0 $Proxy0=new $Proxy0(target, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("========>前置切入");
Object invoke = method.invoke(target, args);
System.out.println("后置切入<==========");
return invoke;
}
});
$Proxy0.sayHello();
}
}
还可以优化,因为实际生成的代理类还继承了一个父类Proxy
public class Proxy implements java.io.Serializable {
...
protected InvocationHandler h;
...
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
...
在这里类里面已经为我们提供了InvocationHandler 保存的地方,因此可以对上面的代理类继续进行优化
public class $Proxy0 extends Proxy implements Target{
private static Method m1;
static {
try {
m1=Target.class.getMethod("sayHello");
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public $Proxy0(InvocationHandler h) {
super(h);
}
@Override
public String sayHello() {
Object ret = null;
try {
//这里将方法具体需要被织入什么样的逻辑交给用户决定
ret = h.invoke(this,m1,null);
}
catch (RuntimeException | Error e)
{
//运行时异常直接抛出
throw e;
}
catch (Throwable e) {
//转换成运行时异常抛出
throw new UndeclaredThrowableException(e);
}
return (String) ret;
}
}
这样就比较接近jdk动态生成的代理类的杨子了
在newProxyInstance方法中通过getProxyClass0方法生成代理类的字节码
Class<?> cl = getProxyClass0(loader, intfs);
该方法底层通过asm字节码框架生成代理类的字节码后,再通过传入的类加载器加载代理类进jvm中
从上面模拟JDK动态代理的简单演示我们可以知道,JDK通过动态代理会生成一个代理类,我们下面通过arthas来反编译一下这个代理类:
public class JdkProxyDemo {
interface Foo {
void foo();
}
static class Target implements Foo {
@Override
public void foo() {
System.out.println("target foo");
}
}
public static void main(String[] param) throws IOException {
// 目标对象
Target target = new Target();
// 代理对象
Foo proxy = (Foo) Proxy.newProxyInstance(
Target.class.getClassLoader(), new Class[]{Foo.class},
(p, method, args) -> {
System.out.println("proxy before...");
Object result = method.invoke(target, args);
System.out.println("proxy after...");
return result;
});
// 调用代理
proxy.foo();
//输出代理类的class类名
System.out.println(proxy.getClass());
//阻塞
System.in.read();
}
}
运行程序,而且要确保这个进程不结束,否则arthas就检测不到了
反编译输出结果如下:
[arthas@22824]$ jad com.jdkProxy.$Proxy0
ClassLoader:
+-sun.misc.Launcher$AppClassLoader@18b4aac2
+-sun.misc.Launcher$ExtClassLoader@74b5721
Location:
/*
* Decompiled with CFR.
*
* Could not load the following classes:
* com.jdkProxy.JdkProxyDemo$Foo
*/
package com.jdkProxy;
import com.jdkProxy.JdkProxyDemo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
final class $Proxy0
extends Proxy
implements JdkProxyDemo.Foo {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public final void foo() {
try {
this.h.invoke(this, m3, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.jdkProxy.JdkProxyDemo$Foo").getMethod("foo", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
}
catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
public final boolean equals(Object object) {
try {
return (Boolean)this.h.invoke(this, m1, new Object[]{object});
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() {
try {
return (Integer)this.h.invoke(this, m0, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}
对于代理类的某个方法来说,如果该方法抛出了运行时异常,那我们直接抛出即可,如果抛出的是编译时异常,那么对应的方法也必须显示抛出该异常:
显然这就改变了原有的方法签名,因此我们不能这样做,可以采用将编译时异常转换为运行时异常抛出的方法来绕过这个问题。
@Override
public String sayHello() {
Object ret = null;
try {
//这里将方法具体需要被织入什么样的逻辑交给用户决定
ret = h.invoke(this,m1,null);
}
catch (RuntimeException | Error e)
{
//运行时异常直接抛出
throw e;
}
catch (Throwable e) {
//转换成运行时异常抛出
throw new UndeclaredThrowableException(e);
}
return (String) ret;
}
因为使用asm框架的要求比较高,需要使用者熟悉jvm指令集,因此我们先手写一个代理类模板,然后通过asm插件,翻译出该类对应的asm代码:
public class $Proxy0 extends Proxy implements Target{
private static Method m1;
static {
try {
m1=Target.class.getMethod("sayHello");
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public $Proxy0(InvocationHandler h) {
super(h);
}
@Override
public String sayHello() {
Object ret = null;
try {
//这里将方法具体需要被织入什么样的逻辑交给用户决定
ret = h.invoke(this,m1,null);
}
catch (RuntimeException | Error e)
{
//运行时异常直接抛出
throw e;
}
catch (Throwable e) {
//转换成运行时异常抛出
throw new UndeclaredThrowableException(e);
}
return (String) ret;
}
}
package asm.com.asm;
import org.springframework.asm.*;
public class $Proxy0Dump implements Opcodes {
public static byte[] dump() throws Exception {
ClassWriter classWriter = new ClassWriter(0);
FieldVisitor fieldVisitor;
RecordComponentVisitor recordComponentVisitor;
MethodVisitor methodVisitor;
AnnotationVisitor annotationVisitor0;
classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "com/asm/$Proxy0", null, "java/lang/reflect/Proxy", new String[]{"com/asm/Target"});
classWriter.visitSource("$Proxy0.java", null);
{
fieldVisitor = classWriter.visitField(ACC_PRIVATE | ACC_STATIC, "m1", "Ljava/lang/reflect/Method;", null, null);
fieldVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(24, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", false);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(25, label1);
methodVisitor.visitInsn(RETURN);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLocalVariable("this", "Lcom/asm/$Proxy0;", null, label0, label2, 0);
methodVisitor.visitLocalVariable("h", "Ljava/lang/reflect/InvocationHandler;", null, label0, label2, 1);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "sayHello", "()Ljava/lang/String;", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
Label label1 = new Label();
Label label2 = new Label();
methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/RuntimeException");
methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/Error");
Label label3 = new Label();
methodVisitor.visitTryCatchBlock(label0, label1, label3, "java/lang/Throwable");
Label label4 = new Label();
methodVisitor.visitLabel(label4);
methodVisitor.visitLineNumber(29, label4);
methodVisitor.visitInsn(ACONST_NULL);
methodVisitor.visitVarInsn(ASTORE, 1);
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(32, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitFieldInsn(GETFIELD, "com/asm/$Proxy0", "h", "Ljava/lang/reflect/InvocationHandler;");
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitFieldInsn(GETSTATIC, "com/asm/$Proxy0", "m1", "Ljava/lang/reflect/Method;");
methodVisitor.visitInsn(ACONST_NULL);
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true);
methodVisitor.visitVarInsn(ASTORE, 1);
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(42, label1);
Label label5 = new Label();
methodVisitor.visitJumpInsn(GOTO, label5);
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(34, label2);
methodVisitor.visitFrame(Opcodes.F_FULL, 2, new Object[]{"com/asm/$Proxy0", "java/lang/Object"}, 1, new Object[]{"java/lang/Throwable"});
methodVisitor.visitVarInsn(ASTORE, 2);
Label label6 = new Label();
methodVisitor.visitLabel(label6);
methodVisitor.visitLineNumber(37, label6);
methodVisitor.visitVarInsn(ALOAD, 2);
methodVisitor.visitInsn(ATHROW);
methodVisitor.visitLabel(label3);
methodVisitor.visitLineNumber(39, label3);
methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"});
methodVisitor.visitVarInsn(ASTORE, 2);
Label label7 = new Label();
methodVisitor.visitLabel(label7);
methodVisitor.visitLineNumber(41, label7);
methodVisitor.visitTypeInsn(NEW, "java/lang/reflect/UndeclaredThrowableException");
methodVisitor.visitInsn(DUP);
methodVisitor.visitVarInsn(ALOAD, 2);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V", false);
methodVisitor.visitInsn(ATHROW);
methodVisitor.visitLabel(label5);
methodVisitor.visitLineNumber(43, label5);
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/String");
methodVisitor.visitInsn(ARETURN);
Label label8 = new Label();
methodVisitor.visitLabel(label8);
methodVisitor.visitLocalVariable("e", "Ljava/lang/Throwable;", null, label6, label3, 2);
methodVisitor.visitLocalVariable("e", "Ljava/lang/Throwable;", null, label7, label5, 2);
methodVisitor.visitLocalVariable("this", "Lcom/asm/$Proxy0;", null, label4, label8, 0);
methodVisitor.visitLocalVariable("ret", "Ljava/lang/Object;", null, label0, label8, 1);
methodVisitor.visitMaxs(4, 3);
methodVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
Label label1 = new Label();
Label label2 = new Label();
methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/NoSuchMethodException");
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(17, label0);
methodVisitor.visitLdcInsn(Type.getType("Lcom/asm/Target;"));
methodVisitor.visitLdcInsn("sayHello");
methodVisitor.visitInsn(ICONST_0);
methodVisitor.visitTypeInsn(ANEWARRAY, "java/lang/Class");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
methodVisitor.visitFieldInsn(PUTSTATIC, "com/asm/$Proxy0", "m1", "Ljava/lang/reflect/Method;");
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(20, label1);
Label label3 = new Label();
methodVisitor.visitJumpInsn(GOTO, label3);
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(18, label2);
methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/NoSuchMethodException"});
methodVisitor.visitVarInsn(ASTORE, 0);
Label label4 = new Label();
methodVisitor.visitLabel(label4);
methodVisitor.visitLineNumber(19, label4);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/NoSuchMethodException", "printStackTrace", "()V", false);
methodVisitor.visitLabel(label3);
methodVisitor.visitLineNumber(21, label3);
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
methodVisitor.visitInsn(RETURN);
methodVisitor.visitLocalVariable("e", "Ljava/lang/NoSuchMethodException;", null, label4, label3, 0);
methodVisitor.visitMaxs(3, 1);
methodVisitor.visitEnd();
}
classWriter.visitEnd();
return classWriter.toByteArray();
}
}
JDK动态代理底层就是通过asm的上述代码,为代理类生成类似于上面的字节码,然后加载进JVM中
public class TestProxy {
public static void main(String[] args) throws Exception {
//得到代理类的二进制流
byte[] dump = $Proxy0Dump.dump();
FileOutputStream os = new FileOutputStream("$Proxy0.class");
os.write(dump, 0, dump.length);
os.close();
}
}
将这个代理类生成的class文件,默认会生成到当前的工作目录下面
下面我们通过类加载器把这个生产的代理类二进制流加载进jvm中,然后创建代理类实例对象,执行其方法:
public class TestProxy {
public static void main(String[] args) throws Exception {
//得到代理类的二进制流
byte[] dump = $Proxy0Dump.dump();
ClassLoader loader = new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return super.defineClass(name, dump, 0, dump.length);
}
};
Class<?> proxyClass = loader.loadClass("com.asm.$Proxy0");
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
//创建出代理类对象----代理类实现了Target接口并且继承了Proxy类----Proxy类的构造函数需要一个参数InvocationHandler
Target proxy = (Target) constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理类执行");
return null;
}
});
proxy.sayHello();
}
}
代理一点都不难,无非就是利用了多态、反射的知识
// 运行时请添加 --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED
public class TestMethodInvoke {
public static void main(String[] args) throws Exception {
Method foo = TestMethodInvoke.class.getMethod("foo", int.class);
for (int i = 1; i <= 17; i++) {
show(i, foo);
foo.invoke(null, i);
}
System.in.read();
}
// 方法反射调用时, 底层 MethodAccessor 的实现类
private static void show(int i, Method foo) throws Exception {
Method getMethodAccessor = Method.class.getDeclaredMethod("getMethodAccessor");
getMethodAccessor.setAccessible(true);
Object invoke = getMethodAccessor.invoke(foo);
if (invoke == null) {
System.out.println(i + ":" + null);
return;
}
Field delegate = Class.forName("jdk.internal.reflect.DelegatingMethodAccessorImpl").getDeclaredField("delegate");
delegate.setAccessible(true);
System.out.println(i + ":" + delegate.get(invoke));
}
public static void foo(int i) {
System.out.println(i + ":" + "foo");
}
}
运行时请添加 --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED
JDK虽然在方法多次反射后,会通过生成代理类进行优化,但是对于每个方法都会生成一个代理类,这有点浪费性能了
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://cjdhy.blog.csdn.net/article/details/123782810
内容来源于网络,如有侵权,请联系作者删除!