首页 > 其他分享 >一Spring框架基础--3动态代理

一Spring框架基础--3动态代理

时间:2023-03-09 14:34:50浏览次数:57  
标签:java 字节 框架 -- Spring 代理 class import public

一Spring框架基础--3动态代理

1.4 代理模式

1.4.1 Java代码执行流程

1.4.1.1 class文件

img

Java编译器编译好Java文件后,产生.class文件在磁盘,该文件是二进制文件,内容只有jvm虚拟机能够识别的机器码。jvm虚拟机读取字节码.class文件,取出二进制数据,由类加载器classloader加载到内存,解析.class文件内信息,生成对应的class对象。

1.4.1.2 Java代码执行流程

image-20210810004333933
1 编译期

javac将源码文件编译为class文件。

2 类加载(运行期)

将*.class文件加载到jvm,并形成class对象,之后可以对class对象实例化并调用。

注意:

类加载可以在运行时动态加载外部类。

引申内容看:

4 类加载过程

5 classloader与双亲委派机制

3 执行过程

两种方式:

(1)解释执行(解释字节码并执行)

速度慢、效率低,但是比(2)省内存

(2)编译为机器码执行

JTT编译,将字节码编译为机器码。

client(c1)模式:

​ 少量性能能开销比高的优化,占内存少,适用于桌面程序。

server(c2)模式:

​ 大量优化,占内存多,会收集大量运行时信息,适用于服务端程序。

4 类加载过程

类的实例化instantiate在“使用”中实现。

image-20230309124124795 image-20230309124142996 image-20230309124158437 image-20230309124216197

总结:

image-20220725102406067

5 classloader与双亲委派机制
image-20230309124249914 image-20230309124306757 image-20230309124325721 image-20230309124339338 image-20230309124355529

1.4.1.3 示例:手动加载class转换为class对象并实例化

a 定义一个Programmer类,并编译

public class Programmer {
 
	public void code()
	{
		System.out.println("I'm a Programmer,Just Coding.....");
	}
}

b 自定义类加载器

package samples;
/**
 * 自定义一个类加载器,用于将字节码转换为class对象
 */
public class MyClassLoader extends ClassLoader {
 
	public Class<?> defineMyClass( byte[] b, int off, int len) 
	{
		return super.defineClass(b, off, len);
	}
	
}

c 然后编译成class文件,在程序中读取字节码,然后转换成相应的class对象,在实例化

package samples;

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()+"\\bin\\samples\\Programmer.class");
          byte[] result = new byte[1024];

          int count = input.read(result);
          // 使用自定义的类加载器将 byte字节码数组转换为对应的class对象
          MyClassLoader loader = new MyClassLoader();
          Class clazz = loader.defineMyClass( result, 0, count);
          //测试加载是否成功,打印class 对象的名称
          System.out.println(clazz.getCanonicalName());

          //实例化一个Programmer对象
          Object o= clazz.newInstance();
          try {
               //调用Programmer的code方法
               clazz.getMethod("code", null).invoke(o, null);
          } catch (IllegalArgumentException | InvocationTargetException
                   | NoSuchMethodException | SecurityException e) {
               e.printStackTrace();
          }
     }
}

1.4.1.4 java对象初始化和实例化区别

image-20220725102454030

1.4.1.5 运行时JVM内存分配

image-20220725102601570

eg:

public class A {
    private int m=2;
    private String str1="youyou";

    public final static String MESS="world";
    static String ms="world";

    public String getName(String input){
     String temp=input;
     return  temp;
    }

    public  static int getId(){return 0;}
}
public class Test {
    public static void main(String[] args) {
        Class clazz=A.class;
        A a=new A();
        A a1=new A();
    }
    public void change(int i)
    {
        i=123;
    }
}

可知,栈内存存储的是调用方法时的局部变量等信息(就是方法内出现的变量,如main方法里的clazz、a、a1,但是没有A.class等信息);堆区存储的是所有的对象和相应的实例变量(不存局部变量等,只存 new A();以及类变量,即非static的,如str1、m等);方法区(又称为静态成员区)存放所有类、static变量、static方法、常量、成员方法等信息,常量池存放一些常量。

image-20220725102727202

内存区域中存储的内容:

image-20220726102239427

image-20220726102309679

1.4.2 运行期代码中生成二进制字节码

由于JVM通过字节码的二进制信息加载类的,那么,如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力了。

img

目前运行期按照java虚拟机规范对class文件组织规则,生成对应二进制字节码。有两个框架ASM,Javassist。

1.4.2.1 ASM

ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

不过ASM在创建class字节码的过程中,操纵的级别是底层JVM的汇编指令级别,这要求ASM使用者要对class组织结构和JVM汇编指令有一定的了解

示例:下面通过ASM 生成下面类Programmer的class字节码

package com.samples;
import java.io.PrintStream;
 
public class Programmer {
 
	public void code()
	{
		System.out.println("I'm a Programmer,Just Coding.....");
	}
}

使用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, "<init>", "()V", null, null);
          mv.visitCode();
          mv.visitVarInsn(Opcodes.ALOAD, 0);
          mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>","()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("D://Programmer.class");
          FileOutputStream fout = new FileOutputStream(file);
          fout.write(data);
          fout.close();
     }
}

上述代码,可以生成字节码,并动态加载生成class对象,后续可以创建实例。

1.2.2.2 Javassist

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

示例:通过Javassist创建上述的Programmer类

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");
	}
}

1.4.3 静态代理

1.4.3 静态代理

image-20230309125454460

代理模式上,基本上有Subject角色,RealSubject角色,Proxy角色。其中:Subject角色负责定义RealSubject和Proxy角色应该实现的接口;RealSubject角色用来真正完成业务服务功能;Proxy角色负责将自身的Request请求,调用realsubject 对应的request功能来实现业务功能,自己不真正做业务。

img

特点:

当在代码阶段规定这种代理关系,Proxy类通过编译器编译成class文件,当系统运行时,此class已经存在了。这种静态的代理模式固然在访问无法访问的资源,增强现有的接口业务功能方面有很大的优点,但是大量使用这种静态代理,会使我们系统内的类的规模增大,并且不易维护;并且由于Proxy和RealSubject的功能 本质上是相同的,Proxy只是起到了中介的作用,这种代理在系统中的存在,导致系统结构比较臃肿和松散。

1.4.4 动态代理

为了解决静态代理的问题,就有了动态地创建Proxy的想法:在运行状态中,需要代理的地方,根据Subject 和RealSubject,动态地创建一个Proxy,用完之后,就会销毁,这样就可以避免了Proxy 角色的class在系统中冗杂的问题了。

由于我们现在不希望静态地有StationProxy类存在,希望在代码中,动态生成器二进制代码,加载进来。为此,使用Javassist开源框架,在代码中动态地生成StationProxy的字节码。

但是,最终发现,使用Javassist框架,其实现相当地麻烦,且在创造的过程中,含有太多的业务代码。我们使用上述创建Proxy代理类的方式的初衷是减少系统代码的冗杂度,但是上述做法却增加了在动态创建代理类过程中的复杂度:手动地创建了太多的业务代码,并且封装性也不够,完全不具有可拓展性和通用性。如果某个代理类的一些业务逻辑非常复杂,上述的动态创建代理的方式是非常不可取的!

1.4.4.1 InvocationHandler角色由来

思考proxy代理类的实质角色:

img

由上图可以看出,代理类处理的逻辑很简单:在调用某个方法前及方法后做一些额外的业务。换一种思路就是:在触发(invoke)真实角色的方法之前或者之后做一些额外的业务。那么,为了构造出具有通用性和简单性的代理类,可以将所有的触发真实角色动作交给一个触发的管理器,让这个管理器统一地管理触发。这种管理器就是InvocationHandler。

动态代理模式的结构跟上面的静态代理模式稍微有所不同,多引入了一个InvocationHandler角色。

InvocationHandler作用:

静态代理中,Proxy所做的事情,无非是调用在不同的request时,调用触发realSubject对应的方法;更抽象点看,Proxy所作的事情,在Java中 方法(Method)也是作为一个对象来看待了(即proxy调用不同方法,实际是调用不同的Method对象,把Method看作一个对象)。

动态代理中,工作的基本模式就是将自己的方法功能的实现交给 InvocationHandler角色,外界对Proxy角色中的每一个方法的调用,Proxy角色都会交给InvocationHandler来处理,而InvocationHandler则调用具体对象角色的方法。如下图所示:

img

该动态代理模式下,要求Proxy代理类和被代理类RealSubject应该实现相同的功能(很重要)。

java中,实现相同功能有两种方式:

a:定义功能接口,让proxy和realsubject共同实现(JDK动态代理机制——通过接口)

b:继承。让proxy继承realsubject,重写其中方法,实现多态(cglib-类继承)

1.4.4.2 JDK动态代理机制——接口

比如现在想为RealSubject这个类创建一个动态代理对象,JDK主要会做以下工作:

1.   获取 RealSubject上的所有接口列表;
2.   确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX ;
3.   根据需要实现的接口信息,在代码中动态创建 该Proxy类的字节码;
4 .  将对应的字节码转换为对应的class 对象;
5.   创建InvocationHandler实例handler,用来处理Proxy所有方法调用;
6.   Proxy 的class对象:以创建的handler对象为参数,实例化一个proxy对象

思路:
先创建proxy类的字节码》字节码生成class对象》创建handler实例》用handler实例化proxy对象

JDK通过 java.lang.reflect.Proxy包来支持动态代理,一般情况下,我们使用下面的newProxyInstance方法

newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);
         //  返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序InvocationHandler

而对于InvocationHandler,我们需要实现下列的invoke方法:

在调用代理对象中的每一个方法时,在代码内部,都是直接调用了InvocationHandler 的invoke方法,而invoke方法根据代理类传递给自己的method参数来区分是什么方法。

invoke(Object proxy,Method method,Object[] args)
          //在代理实例上处理方法调用并返回结果。

示例:

img
package com.foo.proxy;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
 
public class Test {
 
	public static void main(String[] args) {
 
		ElectricCar car = new ElectricCar();
		// 1.获取对应的ClassLoader
		ClassLoader classLoader = car.getClass().getClassLoader();
 
		// 2.获取ElectricCar 所实现的所有接口
		Class[] interfaces = car.getClass().getInterfaces();
		// 3.设置一个来自代理传过来的方法调用请求处理器,处理所有的代理对象上的方法调用
		InvocationHandler handler = new InvocationHandlerImpl(car);
		/*
		  4.根据上面提供的信息,创建代理对象 在这个过程中, 
             a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节码
		         b.然后根据相应的字节码转换成对应的class, 
             c.然后调用newInstance()创建实例
		 */
		Object o = Proxy.newProxyInstance(classLoader, interfaces, handler);
          //由于被代理类实现两个接口,Proxy可以根据interface实现多态转换,并调用
		Vehicle vehicle = (Vehicle) o;
		vehicle.drive();
		Rechargable rechargeable = (Rechargable) o;
		rechargeable.recharge();
	}
}
package com.foo.proxy;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
 
//调用的触发器
public class InvocationHandlerImpl implements InvocationHandler {
 
	private ElectricCar car;
	
     //IH中需要传入被代理类实例
	public InvocationHandlerImpl(ElectricCar car)
	{
		this.car=car;
	}
	
     //调用invoke由proxy执行,根据method对象,调用不同的方法
	@Override
	public Object invoke(Object paramObject, Method paramMethod,
			Object[] paramArrayOfObject) throws Throwable {
		System.out.println("You are going to invoke "+paramMethod.getName()+" ...");
		paramMethod.invoke(car, null);
		System.out.println(paramMethod.getName()+" invocation Has Been finished...");
		return null;
	}
 
}

仔细观察可以看出生成的动态代理类有以下特点:
1.继承自 java.lang.reflect.Proxy,实现了 Rechargable,Vehicle 这两个ElectricCar实现的接口;

2.类中的所有方法都是final 的;

3.所有的方法功能的实现都统一调用了InvocationHandler的invoke()方法。

img

1.4.4.3 cglib——类继承

JDK中提供的生成动态代理类的机制有个鲜明的特点是: 某个类必须有实现的接口,而生成的代理类也只能代理某个类接口定义的方法。

动态代理无法代理的方法:

1 类中实现方法,不在interface中;

2 类没有实现接口,则类就无法用JDK实现动态代理。

cglib 创建某个类A的动态代理类的模式是:

  1. 查找A上的所有非final 的public类型的方法定义;
  2. 将这些方法的定义转换成字节码;
  3. 将组成的字节码转换成相应的代理的class对象;
  4. 实现 MethodInterceptor接口,用来处理 对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)

示例:被代理类program,对被代理类添加操作的Hacker类

package samples;
/**
 * 程序猿类
 */
public class Programmer {
 
	public void code()
	{
		System.out.println("I'm a Programmer,Just Coding.....");
	}
}

首先,实现接口MethodInterceptor;然后,实现intercept方法,传入被代理类(区别:这里interceptor实现类内,不需要传入被代理对象示例,因此是无参构造函数,然后;JDK动态代理InvocationHandler内,需要传入被代理类实例)

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;
	}
 
}

cglib中用加强器Enhancer,设置需要动态代理的类,并设置callback

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();
        
	}
}

标签:java,字节,框架,--,Spring,代理,class,import,public
From: https://www.cnblogs.com/LBJboy/p/17198191.html

相关文章

  • RockWell FTPC process design
    `/**查询数据**/functionquery(){//异常处理functionhandleException(exception){//错误信息handleErrorMsg(MESSAGE_NAME,"QUERY_EXCEPTION",[exception.ge......
  • 94某书参数逆向
    就这里,打断点开始扣(过程就不详细描述了)扣完补补补完成完美解密用execjs调用:解决......
  • 公司职位及英文缩写
    公司高层职位的英文缩写: 首席执行官【CEO】ChiefExecutiveOfficer首席营运官【COO】ChiefOperationOfficer首席财务官【CFO】ChiefFinanceOfficer首席信息官【CIO......
  • JS关于导出文件流方法封装的使用
    如果需要频繁地在JavaScript中导出文件流,可以将上述的方法封装为函数,以便在不同的代码中调用。/***导出文件*@param{string|Uint8Array}data-文件内容*@para......
  • vnc安装启动
    一、安装配置1、  sudoaptinstalltigervnc-standalone-server2、sudoaptinstallxfce4   #注意安装过程中会跳出一个图形界面,让选择,选择light的3、vim~/.......
  • MySQL开放远程连接权限
    创建一个新的MySQL用户,命令行登录mysql,通过语句创建一个新用户CREATEUSER'username'@'%'IDENTIFIEDBY'your_user_password';让我们授予这个用户一些数据库权限,例......
  • java collections的概述和使用
      ......
  • static静态局部变量
    static静态局部变量1.static1.1分类函数内部:用static关键字定义的变量称为静态局部变量函数外部:用static关键字定义的变量称为静态全局变量1.2区别静态局部变量只......
  • 最受欢迎的大数据可视化
    大数据可视化​是进行各种大数据分析的最重要组成部分之一。一旦原始数据流被以图像形式表示时,以此做决策就变得容易多了。为了满足并超越客户的期望,大数据可视化工具......
  • 手把手带你玩转Linux
    今天这篇文章带你走进Linux世界的同时,带你手把手玩转Linux,加深对Linux系统的认识。一、搞好Linux工作必须得不断折腾,说白了,只是动手力量必须强。我在初学Linux的那片,家......