详解:动态代理中的不同实现方式

久未见 提交于 2019-11-29 19:11:44

动态代理的实现

1.JDK动态代理

jdk动态代理实现步骤:

前提:jdk动态代理有限制条件,要代理的目标对象必须要实现接口

实现:使用反射API实现,具体实现原理这里不做详细讲解,这里只讲解动态代理的实现。

以下为代码列表,所有涉及到的类有三个

  • Calculator.java 【目标对象实现的接口】
  • CalculatorImpl.java 【目标对象】
  • Main.java 【程序入口类】
//目标对象实现的接口  public interface Calculator {   int add(int a, int b);  }
//目标对象  public class CalculatorImpl implements Calculator {   @Override   public int add(int a, int b) {   return a + b;   }  }
​  //程序入口  import java.lang.reflect.Proxy;  ​  public class Main {   public static void main(String[] args) {   //创建目标对象   Calculator calculator = new CalculatorImpl();   //创建代理对象   Calculator o = (Calculator) Proxy.newProxyInstance(calculator.getClass().getClassLoader(), new Class[]{Calculator.class}, (proxy, method, args1) -> {   //定义其他的程序流程   System.out.println("the method " + method.getName() + " is running ...");   //执行目标方法   return method.invoke(calculator, args1);   });   o.add(2, 3);   }  }

这三个类中,接口和目标对象没什么说的,都很简单。核心代码其实就一行

Calculator o = (Calculator) Proxy.newProxyInstance(calculator.getClass().getClassLoader(), new Class[]{Calculator.class}, (proxy, method, args1) -> {   System.out.println("the method " + method.getName() + " is running ...");   return method.invoke(calculator, args1);   });

对应的API就是

//返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。  public static Object newProxyInstance(ClassLoader loader,//类加载器   Class<?>[] interfaces,//目标对象实现的接口   InvocationHandler h)//调用处理程序   throws IllegalArgumentException

2.CGLIB动态代理

cglib动态代理不需要目标类继承其它类或者实现接口,利用asm开源包,对目标对象类的class文件加载进来,通过修改其字节码生成子类来处理。实际上cglib的api和jdk动态代理api很类似。

//目标对象,这里没有实现接口,所以用CGLIB代理实现  public class Calculator{   public int add(int a, int b) {   return a + b;   }  }
//程序入口  import org.springframework.cglib.proxy.Enhancer;  import org.springframework.cglib.proxy.MethodInterceptor;  ​  public class Main {   public static void main(String[] args) {   Enhancer enhancer = new Enhancer();   enhancer.setSuperclass(Calculator.class);   //public void setCallback(final Callback callback)   //这个方法需要Callback回调,这是一个接口,常用的两个实现   //1.MethodInterceptor接口,内部是intercept方法,形参如下:   //o为返回的代理对象,不常用,容易抛出堆栈溢出   //method为目标方法的反射类型,可以获取当前执行的目标方法的方法名   //objects为object数组,实际为目标方法的参数列表   //methodProxy用于调用初始方法,或者是调用其它类的同名方法【不同于第二个参数method,不用创建目标对象】      //2.InvocationHandler接口,内部是invoke方法,形参如下:   //invoke(java.lang.Object o, java.lang.reflect.Method method, java.lang.Object[] objects)   //o为返回的代理对象,不常用,容易抛出堆栈溢出   //method为目标方法的反射类型   //objects为参数列表      enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {   String name = method.getName();   //目标方法   System.out.println("方法" + name + "正在运行");   //目标方法的参数列表   System.out.println("参数列表:"+objects);   //Object result = method.invoke(o, objects); //方法抛出堆栈异常,需要创建目标对象作为形参   Object result = methodProxy.invokeSuper(o, objects);   System.out.println("执行结果:"+result);   return result;   });   //创建代理对象   Calculator proxyObject = (Calculator) enhancer.create();   // System.out.println(proxyObject);   proxyObject.add(1, 2);   }  }

3.总结jdk动态代理和cglib代理

jdk动态代理需要目标对象实现接口

cglib代理不需要目标对象实现接口或者继承对象

jdk动态代理使用的是反射的API

cglib代理使用asm解析并修改字节码文件,生成的代理对象本质是目标对象的子类

两种方式都是运行时动态绑定的。

4.aspectj代理实现

Spring AOP本质就是配合使用JDK Proxy动态代理和CGLIB工具,从而实现方法的切入。Spring会优先使用JDK动态代理,当调用方法不是接口方法时,选择使用CGLIB。这一章节使用aspectj的注解配置来实现动态代理。

Spring AOP 和 Aspectj对比文章请参考:

https://juejin.im/post/5a695b3cf265da3e47449471

//目标对象  @Component  public class Calculator2 {  ​   public int add(int a, int b) {   return a + b;   }  }
​  //切面  @Component  @Aspect  public class LoggingAspect2 {   /**   * 定义切入点表达式,方便管理和代码复用   */   @Pointcut(value = "execution(* aspectj.*.*.*(..))")   public void declarePointcutExpression() {   //此方法内部不能有任何代码,只是用于标记Pointcut注解   }  ​   /**   * 前置通知,在方法被执行前调用   *   * @param joinPoint 连接点   */   @Before(value = "declarePointcutExpression()")   public void beforeExecute(JoinPoint joinPoint) {   String name = joinPoint.getSignature().getName();   System.out.println("前置通知:在" + name + "方法【前】被执行...");   }  ​   /**   * 后置通知,在方法执行后调用,不论方法是否抛出异常,都会调用   *   * @param joinPoint 连接点   */   @After(value = "declarePointcutExpression()")   public void afterExecute(JoinPoint joinPoint) {   String name = joinPoint.getSignature().getName();   System.out.println("后置通知:在" + name + "方法【后】被执行...");   }  ​   /**   * 返回通知,在方法正常执行完毕,返回后调用,可以获取返回值   *   * @param joinPoint 连接点   */   @AfterReturning(value = "declarePointcutExpression()", returning = "result")   public void afterReturning(JoinPoint joinPoint, Object result) {   String name = joinPoint.getSignature().getName();   System.out.println("正常返回通知:在" + name + "方法【正常执行返回后通知】被执行...");   System.out.println("----------------------方法返回值:---" + result + "----------");   }  ​   /**   * 抛出异常通知,在方法抛出异常后执行,可以限定方法在抛出指定的异常之后才执行,可以获取异常信息   *   * @param joinPoint 连接点   */   @AfterThrowing(value = "declarePointcutExpression()", throwing = "e")   public void afterThrowing(JoinPoint joinPoint, Throwable e) {   String name = joinPoint.getSignature().getName();   System.out.println("发出异常通知:在" + name + "方法【抛出异常】被执行");   System.out.println("----------------------获取异常信息:---" + e.toString() + "-----------");   }  ​   /**   * 环绕通知:在方法的前后都可以定义代码块执行,功能最强   */   @Around(value = "declarePointcutExpression()")   public Object around(ProceedingJoinPoint proceedingJoinPoint) {   String name = proceedingJoinPoint.getSignature().getName();   System.out.println("环绕通知:在" + name + "方法前后都可以执行代码块");   System.out.println("环绕通知:此为方法调用前的输出日志....");   Object result = null;   try {   //调用目标对象的对应方法   result = proceedingJoinPoint.proceed();   System.out.println("环绕通知:此为方法调用后的输出日志....");   System.out.println("-----------------------------------环绕通知获取返回值-------" + result + "------------");   } catch (Throwable throwable) {   System.out.println("环绕通知:此为方法抛出异常信息之后的输出日志....");   System.out.println("------------------------------------环绕通知获取异常信息-----------" + throwable + "---------------------------");   throwable.printStackTrace();   }   System.out.println("环绕通知:此为方法正常返回时的输出日志...");   return result;   }  }
​  //测试类  @SpringBootTest  @RunWith(SpringJUnit4ClassRunner.class)  public class CalculatorImpl2Test {   @Autowired   private Calculator2 calculator;  ​   @Test   public void add() {   calculator.add(1, 1);   }  }

项目源码:

https://github.com/lingEric/dynamicproxy

最后小编这里分享一些资料给大家,对学习提升很有作用的,有关于分布式,微服务,性能优化,Spring,MyBatis的等源码知识点的录像视频还有spring, jvm等等的面试题,希望能够帮助到大家!

需要的话可以

我是小架,我们下篇文章再见!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!