Java代理
1、Java代理-概念
1、Java代理(Proxy): 是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象。[扩展目标对象的功能]
2、代理模式的关键点是:代理对象[对目标对象的扩展]与目标对象[被代理对象调用的]。
3、Java代理-实现方式主要有:
3-1、静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。[需要代理类和被代理类实现相同的接口。]
3-2、动态代理:
3-2-1、代理对象,不需要实现接口;
3-2-2、代理对象的生成利用JDK的API[动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)]
3-2-3、代理类在程序运行时创建的代理方式被成为动态代理。
3-2-4、动态代理分为:JDK动态代理(基于拦截器和反射来实现。)、Cglib动态代理、
1.1、代理的结构构成
1、代理模式上,基本上有Subject角色,RealSubject角色,Proxy角色。
1-1、其中:Subject角色负责定义RealSubject和Proxy角色应该实现的接口;
1-2、RealSubject角色用来真正完成业务服务功能;
1-3、Proxy角色负责将自身的Request请求,调用realsubject 对应的request功能来实现业务功能,自己不真正做业务。
1.2、InvocationHandler角色的由来
1、动态代理模式的结构跟上面的静态代理模式稍微有所不同,多引入了一个InvocationHandler角色。
2、InvocationHandler角色的作用
2-1、动态代理工作的基本模式就是将自己的方法功能的实现交给 InvocationHandler角色,外界对Proxy角色中的每一个方法的调用,Proxy角色都会交给InvocationHandler来处理,而InvocationHandler则调用具体对象角色的方法[.invoke()方法]。
2、动态代理-简介
2.1、class文件-简介及加载
1、class文件-简介
1-1、Java编译器编译好Java文件之后,产生.class 文件在磁盘中。
1-2、这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码。
1-3、JVM虚拟机读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息,生成对应的 Class对象。
1-4、在运行时期可以按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码。当前有很多开源框架可以完成这些功能,如ASM,Javassist。
2、class文件-加载
2-1、Java编译环境
Java源代码(.java文件)-->Java编译器-->Java字节码(.class文件)-->
2-2、Java运行期环境
类装载器(Java类库-字节码验证)-->Java虚拟机(Java解析器和即时编译器)-->
2.2、Java字节码生成开源框架介绍—ASM
1、ASM-概述
1-1、ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。
1-2、ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。
1-3、ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
1-4、ASM在创建class字节码的过程中,操纵的级别是底层JVM的汇编指令级别。
2.2.1、ASM框架-举例实践Demo
1、创建一个类:
public class Programmer {
public void code(){
System.out.println("I'm a Programmer,Just Coding.....");
}
}
2、使用ASM框架提供了ClassWriter 接口,通过访问者模式进行动态创建class字节码
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();
}
}
3、验证
3-1、用Java反编译工具(如: JD_GUI)打开D盘下生成的Programmer.class
3-2、定义的类加载器将这个class文件加载到内存中,然后 创建class对象,并且实例化一个对象,调用code方法。
2.3、Java字节码生成开源框架介绍—Javassist:
1、Javassist-概述
1-1、Javassist是一个开源的分析、编辑和创建Java字节码的类库。
1-2、通过使用Javassist对字节码操作为JBoss实现动态AOP框架。
1-3、javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。
1-3-1、直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
1-3-2、javassist 通过添加java代码 可以很方便的构建代理类,当添加了一段java格式源码,会通过 jdk api Javac 编译生成对应字节码,最后拼接成完整的类字节码,然后调用 jdk api defindClass生成代理类。
2.3.1、Javassist框架-举例实践Demo
1、通过Javassist创建上述的Programmer类
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");
}
}
2、验证
2-1、通过JD-gui反编译工具打开Programmer.class
2.3.2、Javassist框架-使用简介
javassist使用简介
2.4、Java代理-JDK动态代理-概念
1、jdk动态代理是jre提供给我们的类库,可以直接使用,不依赖第三方。
1-1、必须实现InvocationHandler接口;
1-2、使用Proxy.newProxyInstance产生代理对象;
1-3、被代理的对象必须要实现接口;
2、在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,
3、通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
4、创建动态代理对象步骤:
4-1、实现InvocationHandler接口来自定义自己的InvocationHandler
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
4-2、使用Proxy类的getProxyClass静态方法生成一个动态代理类stuProxyClass
Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});
4-3、过反射机制获得代理类的构造方法.getConstructor()
Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);
4-4、通过构造函数获得代理对象并将自定义的InvocationHandler实例对象传为参数传入,创建一个动态实例stuProxy
Person stuProxy = (Person) cons.newInstance(stuHandler);
4-5、步骤简化:通过Proxy类的newProxyInstances方法
Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
4-6、通过代理对象调用目标方法
stuProxy.xxx();
5、源码示例:
public class StarProxy implements InvocationHandler{
// 目标类,也就是被代理对象
private Object target;
public void setTarget(Object target)
{
this.target = target;
}
/**
* proxy 代理对象引用
* method 执行目标的方法
* args 目标方法执行时的入参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
// 这里可以做增强
System.out.println("收钱");
Object result = method.invoke(target, args);
return result;
}
// 生成代理类
public Object CreatProxyedObj(){
//生成代理类,有两个参数,前两个参数表示在同一个classloader下通过接口创建出一个对象,该对象需要一个属性,也就是第三个参数,它是一个InvocationHandler。
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
}
6、JDK动态代理总结:
6-1、生成的代理类:$Proxy0 extends Proxy implements Person,我们看到代理类继承了Proxy类,所以也就决定了java动态代理只能对接口进行代理。
6-2、通过组装字节码,生成代理类字节码:java.lang.reflect.Proxy 有传入字节码加载类的native方, 调用它生成代理类。
7、JDK动态代理应用举例:
Spring的AOP实现其实也是用了Proxy和InvocationHandler这两个东西的。
2.5、Java代理-Cglib动态代理-概念
1、Cglib(Code Generation Library)代理,也叫作子类代理,使用以目标对象子类的方式类实现代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
1-1、是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。
1-2、底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。
2、需要引入cglib的jar文件,--Spring-core-x.x.x.jar
3、继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强。
4、利用ASM开源包(除了CGLIB包,脚本语言例如Groovy和BeanShell,也是使用ASM来生成java的字节码。),对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
源码示例:
public class CglibProxy implements MethodInterceptor{
// 根据一个类型产生代理类,此方法不要求一定放在MethodInterceptor中
public Object CreatProxyedObj(Class<?> clazz){
//工具类
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(clazz);
//设置回调函数
enhancer.setCallback(this);
//创建子类[代理对象]
return enhancer.create();
}
//调用MethodInterceptor的intercept方法,
//调用时会传递四个参数,第一个参数传递的是this,代表代理类本身,第二个参数标示拦截的方法,第三个参数是入参,第四个参数是cglib方法,intercept方法完成增强后,我们调用cglib方法间接调用父类方法完成整个方法链的调用。
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable{
// 这里增强
System.out.println("收钱");
return arg3.invokeSuper(arg0, arg2);
}
}
2.6、Java代理-动态代理-比较
1、JDK和CGLIB动态代理原理
1-1、JDK动态代理:利用拦截器(拦截器必须实现InvocationHanlder)加上[反射机制]生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
1-2、CGLIB动态代理:[利用ASM开源包],对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
1-3、如果目标对象[实现了接口],默认情况下会采用JDK的动态代理实现AOP,也可以强制使用Cglib实现AOP。
1-4、如果目标对象[没有实现了接口],必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。
1-5、JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
1-6、CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。
1-7、jdk的动态代理和cglib的动态代理,都是通过运行时动态生成字节码的方式来实现代理的。
2、Spring如何强制使用CGLIB实现AOP?
2-1、添加CGLIB库(aspectjrt-xxx.jar、aspectjweaver-xxx.jar、cglib-nodep-xxx.jar)
2-2、在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
3、JDK和CGLIB动态代理-比较
3-1、JDK动态代理必须有接口,CGLIB不需要
3-2、JDK动态代理通过继承实现,CGLIB通过组合实现。
2.7、Java代理-动态代理-资料
1、代码Demo: https://github.com/yihonglei/thinking-in-spring(spring工程)
2、JDK代理原理源码分析: https://blog.csdn.net/yhl_jxy/article/details/80586785
3、CGLIB代理原理源码分析:https://blog.csdn.net/yhl_jxy/article/details/80633194