一Spring框架基础--3动态代理
一Spring框架基础--3动态代理
1.4 代理模式
1.4.1 Java代码执行流程
1.4.1.1 class文件
Java编译器编译好Java文件后,产生.class文件在磁盘,该文件是二进制文件,内容只有jvm虚拟机能够识别的机器码。jvm虚拟机读取字节码.class文件,取出二进制数据,由类加载器classloader加载到内存,解析.class文件内信息,生成对应的class对象。
1.4.1.2 Java代码执行流程
1 编译期
javac将源码文件编译为class文件。
2 类加载(运行期)
将*.class文件加载到jvm,并形成class对象,之后可以对class对象实例化并调用。
注意:
类加载可以在运行时动态加载外部类。
引申内容看:
4 类加载过程
5 classloader与双亲委派机制
3 执行过程
两种方式:
(1)解释执行(解释字节码并执行)
速度慢、效率低,但是比(2)省内存
(2)编译为机器码执行
JTT编译,将字节码编译为机器码。
client(c1)模式:
少量性能能开销比高的优化,占内存少,适用于桌面程序。
server(c2)模式:
大量优化,占内存多,会收集大量运行时信息,适用于服务端程序。
4 类加载过程
类的实例化instantiate在“使用”中实现。
总结:
5 classloader与双亲委派机制
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对象初始化和实例化区别
1.4.1.5 运行时JVM内存分配
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方法、常量、成员方法等信息,常量池存放一些常量。
内存区域中存储的内容:
1.4.2 运行期代码中生成二进制字节码
由于JVM通过字节码的二进制信息加载类的,那么,如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力了。
目前运行期按照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 静态代理
代理模式上,基本上有Subject角色,RealSubject角色,Proxy角色。其中:Subject角色负责定义RealSubject和Proxy角色应该实现的接口;RealSubject角色用来真正完成业务服务功能;Proxy角色负责将自身的Request请求,调用realsubject 对应的request功能来实现业务功能,自己不真正做业务。
特点:
当在代码阶段规定这种代理关系,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代理类的实质角色:
由上图可以看出,代理类处理的逻辑很简单:在调用某个方法前及方法后做一些额外的业务。换一种思路就是:在触发(invoke)真实角色的方法之前或者之后做一些额外的业务。那么,为了构造出具有通用性和简单性的代理类,可以将所有的触发真实角色动作交给一个触发的管理器,让这个管理器统一地管理触发。这种管理器就是InvocationHandler。
动态代理模式的结构跟上面的静态代理模式稍微有所不同,多引入了一个InvocationHandler角色。
InvocationHandler作用:
静态代理中,Proxy所做的事情,无非是调用在不同的request时,调用触发realSubject对应的方法;更抽象点看,Proxy所作的事情,在Java中 方法(Method)也是作为一个对象来看待了(即proxy调用不同方法,实际是调用不同的Method对象,把Method看作一个对象)。
动态代理中,工作的基本模式就是将自己的方法功能的实现交给 InvocationHandler角色,外界对Proxy角色中的每一个方法的调用,Proxy角色都会交给InvocationHandler来处理,而InvocationHandler则调用具体对象角色的方法。如下图所示:
该动态代理模式下,要求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)
//在代理实例上处理方法调用并返回结果。
示例:
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()方法。
1.4.4.3 cglib——类继承
JDK中提供的生成动态代理类的机制有个鲜明的特点是: 某个类必须有实现的接口,而生成的代理类也只能代理某个类接口定义的方法。
动态代理无法代理的方法:
1 类中实现方法,不在interface中;
2 类没有实现接口,则类就无法用JDK实现动态代理。
cglib 创建某个类A的动态代理类的模式是:
- 查找A上的所有非final 的public类型的方法定义;
- 将这些方法的定义转换成字节码;
- 将组成的字节码转换成相应的代理的class对象;
- 实现 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();
}
}