Java代理

1、Java代理-概念

  1. 1Java代理(Proxy): 是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象。[扩展目标对象的功能]
  2. 2、代理模式的关键点是:代理对象[对目标对象的扩展]与目标对象[被代理对象调用的]。
  3. 3Java代理-实现方式主要有:
  4. 3-1、静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。[需要代理类和被代理类实现相同的接口。]
  5. 3-2、动态代理:
  6. 3-2-1、代理对象,不需要实现接口;
  7. 3-2-2、代理对象的生成利用JDKAPI[动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)]
  8. 3-2-3、代理类在程序运行时创建的代理方式被成为动态代理。
  9. 3-2-4、动态代理分为:JDK动态代理(基于拦截器和反射来实现。)、Cglib动态代理、

1.1、代理的结构构成

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

1.2、InvocationHandler角色的由来

  1. 1、动态代理模式的结构跟上面的静态代理模式稍微有所不同,多引入了一个InvocationHandler角色。
  2. 2InvocationHandler角色的作用
  3. 2-1、动态代理工作的基本模式就是将自己的方法功能的实现交给 InvocationHandler角色,外界对Proxy角色中的每一个方法的调用,Proxy角色都会交给InvocationHandler来处理,而InvocationHandler则调用具体对象角色的方法[.invoke()方法]。

2、动态代理-简介

2.1、class文件-简介及加载

  1. 1class文件-简介
  2. 1-1Java编译器编译好Java文件之后,产生.class 文件在磁盘中。
  3. 1-2、这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码。
  4. 1-3JVM虚拟机读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息,生成对应的 Class对象。
  5. 1-4、在运行时期可以按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码。当前有很多开源框架可以完成这些功能,如ASMJavassist
  6. 2class文件-加载
  7. 2-1Java编译环境
  8. Java源代码(.java文件)-->Java编译器-->Java字节码(.class文件)-->
  9. 2-2Java运行期环境
  10. 类装载器(Java类库-字节码验证)-->Java虚拟机(Java解析器和即时编译器)-->

2.2、Java字节码生成开源框架介绍—ASM

  1. 1ASM-概述
  2. 1-1ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。
  3. 1-2ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。
  4. 1-3ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
  5. 1-4ASM在创建class字节码的过程中,操纵的级别是底层JVM的汇编指令级别。

2.2.1、ASM框架-举例实践Demo

  1. 1、创建一个类:
  2. public class Programmer {
  3. public void code(){
  4. System.out.println("I'm a Programmer,Just Coding.....");
  5. }
  6. }
  7. 2、使用ASM框架提供了ClassWriter 接口,通过访问者模式进行动态创建class字节码
  8. public class MyGenerator {
  9. public static void main(String[] args) throws IOException {
  10. System.out.println();
  11. ClassWriter classWriter = new ClassWriter(0);
  12. // 通过visit方法确定类的头部信息
  13. classWriter.visit(Opcodes.V1_7,// java版本
  14. Opcodes.ACC_PUBLIC,// 类修饰符
  15. "Programmer", // 类的全限定名
  16. null, "java/lang/Object", null);
  17. MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC,
  18. "<init>", "()V", null, null);
  19. mv.visitCode();
  20. mv.visitVarInsn(Opcodes.ALOAD, 0);
  21. mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>",
  22. "()V");
  23. mv.visitInsn(Opcodes.RETURN);
  24. mv.visitMaxs(1, 1);
  25. mv.visitEnd();
  26. // 定义code方法
  27. MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC,
  28. "code", "()V", null, null);
  29. methodVisitor.visitCode();
  30. methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System",
  31. "out", "Ljava/io/PrintStream;");
  32. methodVisitor.visitLdcInsn("I'm a Programmer,Just Coding.....");
  33. methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
  34. "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
  35. methodVisitor.visitInsn(Opcodes.RETURN);
  36. methodVisitor.visitMaxs(2, 2);
  37. methodVisitor.visitEnd();
  38. classWriter.visitEnd();
  39. // 使classWriter类已经完成
  40. // 将classWriter转换成字节数组写到文件里面去
  41. byte[] data = classWriter.toByteArray();
  42. File file = new File("D://Programmer.class");
  43. FileOutputStream fout = new FileOutputStream(file);
  44. fout.write(data);
  45. fout.close();
  46. }
  47. }
  48. 3、验证
  49. 3-1、用Java反编译工具(如: JD_GUI)打开D盘下生成的Programmer.class
  50. 3-2、定义的类加载器将这个class文件加载到内存中,然后 创建class对象,并且实例化一个对象,调用code方法。

2.3、Java字节码生成开源框架介绍—Javassist:

  1. 1Javassist-概述
  2. 1-1Javassist是一个开源的分析、编辑和创建Java字节码的类库。
  3. 1-2、通过使用Javassist对字节码操作为JBoss实现动态AOP框架。
  4. 1-3javassistjboss的一个子项目,其主要的优点,在于简单,而且快速。
  5. 1-3-1、直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
  6. 1-3-2javassist 通过添加java代码 可以很方便的构建代理类,当添加了一段java格式源码,会通过 jdk api Javac 编译生成对应字节码,最后拼接成完整的类字节码,然后调用 jdk api defindClass生成代理类。

2.3.1、Javassist框架-举例实践Demo

  1. 1、通过Javassist创建上述的Programmer
  2. public class MyGenerator {
  3. public static void main(String[] args) throws Exception {
  4. ClassPool pool = ClassPool.getDefault();
  5. //创建Programmer类
  6. CtClass cc = pool.makeClass("com.samples.Programmer");
  7. //定义code方法
  8. CtMethod method = CtNewMethod.make("public void code(){}", cc);
  9. //插入方法代码
  10. method.insertBefore(
  11. "System.out.println(\"I'm a Programmer,Just Coding.....\");");
  12. cc.addMethod(method);
  13. //保存生成的字节码
  14. cc.writeFile("d://temp");
  15. }
  16. }
  17. 2、验证
  18. 2-1、通过JD-gui反编译工具打开Programmer.class

2.3.2、Javassist框架-使用简介

javassist使用简介

2.4、Java代理-JDK动态代理-概念

  1. 1jdk动态代理是jre提供给我们的类库,可以直接使用,不依赖第三方。
  2. 1-1、必须实现InvocationHandler接口;
  3. 1-2、使用Proxy.newProxyInstance产生代理对象;
  4. 1-3、被代理的对象必须要实现接口;
  5. 2、在javajava.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,
  6. 3、通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
  7. 4、创建动态代理对象步骤:
  8. 4-1、实现InvocationHandler接口来自定义自己的InvocationHandler
  9. //创建一个与代理对象相关联的InvocationHandler
  10. InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
  11. 4-2、使用Proxy类的getProxyClass静态方法生成一个动态代理类stuProxyClass
  12. Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});
  13. 4-3、过反射机制获得代理类的构造方法.getConstructor()
  14. Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);
  15. 4-4、通过构造函数获得代理对象并将自定义的InvocationHandler实例对象传为参数传入,创建一个动态实例stuProxy
  16. Person stuProxy = (Person) cons.newInstance(stuHandler);
  17. 4-5、步骤简化:通过Proxy类的newProxyInstances方法
  18. Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
  19. 4-6、通过代理对象调用目标方法
  20. stuProxy.xxx();
  21. 5、源码示例:
  22. public class StarProxy implements InvocationHandler{
  23. // 目标类,也就是被代理对象
  24. private Object target;
  25. public void setTarget(Object target)
  26. {
  27. this.target = target;
  28. }
  29. /**
  30. * proxy 代理对象引用
  31. * method 执行目标的方法
  32. * args 目标方法执行时的入参
  33. */
  34. @Override
  35. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
  36. // 这里可以做增强
  37. System.out.println("收钱");
  38. Object result = method.invoke(target, args);
  39. return result;
  40. }
  41. // 生成代理类
  42. public Object CreatProxyedObj(){
  43. //生成代理类,有两个参数,前两个参数表示在同一个classloader下通过接口创建出一个对象,该对象需要一个属性,也就是第三个参数,它是一个InvocationHandler。
  44. return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
  45. }
  46. }
  47. 6JDK动态代理总结:
  48. 6-1、生成的代理类:$Proxy0 extends Proxy implements Person,我们看到代理类继承了Proxy类,所以也就决定了java动态代理只能对接口进行代理。
  49. 6-2、通过组装字节码,生成代理类字节码:java.lang.reflect.Proxy 有传入字节码加载类的native方, 调用它生成代理类。
  50. 7JDK动态代理应用举例:
  51. SpringAOP实现其实也是用了ProxyInvocationHandler这两个东西的。

2.5、Java代理-Cglib动态代理-概念

  1. 1Cglib(Code Generation Library)代理,也叫作子类代理,使用以目标对象子类的方式类实现代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
  2. 1-1、是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。
  3. 1-2、底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。
  4. 2、需要引入cglibjar文件,--Spring-core-x.x.x.jar
  5. 3、继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强。
  6. 4、利用ASM开源包(除了CGLIB包,脚本语言例如GroovyBeanShell,也是使用ASM来生成java的字节码。),对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
  7. 源码示例:
  8. public class CglibProxy implements MethodInterceptor{
  9. // 根据一个类型产生代理类,此方法不要求一定放在MethodInterceptor中
  10. public Object CreatProxyedObj(Class<?> clazz){
  11. //工具类
  12. Enhancer enhancer = new Enhancer();
  13. //设置父类
  14. enhancer.setSuperclass(clazz);
  15. //设置回调函数
  16. enhancer.setCallback(this);
  17. //创建子类[代理对象]
  18. return enhancer.create();
  19. }
  20. //调用MethodInterceptor的intercept方法,
  21. //调用时会传递四个参数,第一个参数传递的是this,代表代理类本身,第二个参数标示拦截的方法,第三个参数是入参,第四个参数是cglib方法,intercept方法完成增强后,我们调用cglib方法间接调用父类方法完成整个方法链的调用。
  22. @Override
  23. public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable{
  24. // 这里增强
  25. System.out.println("收钱");
  26. return arg3.invokeSuper(arg0, arg2);
  27. }
  28. }

2.6、Java代理-动态代理-比较

  1. 1JDKCGLIB动态代理原理
  2. 1-1JDK动态代理:利用拦截器(拦截器必须实现InvocationHanlder)加上[反射机制]生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
  3. 1-2CGLIB动态代理:[利用ASM开源包],对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
  4. 1-3、如果目标对象[实现了接口],默认情况下会采用JDK的动态代理实现AOP,也可以强制使用Cglib实现AOP
  5. 1-4、如果目标对象[没有实现了接口],必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。
  6. 1-5JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
  7. 1-6CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。
  8. 1-7jdk的动态代理和cglib的动态代理,都是通过运行时动态生成字节码的方式来实现代理的。
  9. 2Spring如何强制使用CGLIB实现AOP
  10. 2-1、添加CGLIB库(aspectjrt-xxx.jaraspectjweaver-xxx.jarcglib-nodep-xxx.jar)
  11. 2-2、在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
  12. 3JDKCGLIB动态代理-比较
  13. 3-1JDK动态代理必须有接口,CGLIB不需要
  14. 3-2JDK动态代理通过继承实现,CGLIB通过组合实现。

2.7、Java代理-动态代理-资料

  1. 1、代码Demo: https://github.com/yihonglei/thinking-in-spring(spring工程)
  2. 2JDK代理原理源码分析: https://blog.csdn.net/yhl_jxy/article/details/80586785
  3. 3CGLIB代理原理源码分析:https://blog.csdn.net/yhl_jxy/article/details/80633194