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

动态代理的实现

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

 

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等等的面试题,希望能够帮助到大家!

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

 

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

 

需要的话,请加我的交流群772300343

即可领取!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值