设计模式

QA

动态代理是什么?有哪些应用?

动态代理是运行时动态生成代理类。

动态代理的应用有 spring aop、hibernate 数据查询、测试框架的后端 mock、rpc,Java注解对象获取等。

怎么实现动态代理?

JDK 原生动态代理和 cglib 动态代理。

  • JDK 原生动态代理是基于接口实现的 
  • !!!重要的两个类!!!
    Proxy类
       Proxy.newProxyInstance 用来生成代理实例
    InvocationHandler接口
       要@Override invoke()方法,拦截所有原始类的各种方法,可在其前后增加逻辑
  • 而 cglib 是基于继承当前类的子类实现的
  • !!!重要的两个类!!!
    Enhancer类
       Enhancer.create(clazz, this) 获取被代理后的目标类
    MethodInterceptor接口
       要@Override intercept()方法,拦截所有原始对象的各种方法,可在其前后增加逻辑

1-为什么需要代理?

当想给一个类的每个method进行扩充(统计、打印耗时)、拦截(判断是否登录)时:

常规思路

  • 修改了原来的代码逻辑
  • 每个涉及的method都需要改动,改动量很大

代理思路

可以使用代理(静态代理/动态代理),统一处理这类额外功能(非核心业务功能),让原来的代码仍然只关心最核心的业务逻辑。

2-静态代理

关键点

代理类ServiceImplProxy和被代理类ServiceImpl实现同样的Service接口

代理类ServiceImplProxy持有被代理类ServiceImpl的引用

代理类ServiceImplProxy在接口方法中,填充扩展/拦截功能,核心逻辑依然由被代理类ServiceImpl的引用来完成

  

代码实例

接口 service

public interface Service {
 
  void play(String xxx);
  
  String run(String xxx);
  
}

被代理类 ServiceImpl

public class ServiceImpl implements Service{

	@Override
	public void play(String xxx){
  	//....
	}

	@Override
	public String run(String xxx){
  	//...
	}

}

代理类 ServiceImplProxy

public class ServiceImplProxy implements Service{

	private Service service;
	
	//***关键*** 持有ServiceImpl的引用
	public ServiceImplProxy() {
		service = new ServiceImpl(); 
	}

	/**
	* 扩展功能
	*/
	@Override
	public void play(String xxx){
		long start = System.currentTimeMillis();//计时开始

		//***关键*** 执行真正的逻辑
		service.play(xxx);

  		long end = System.currentTimeMillis();//计时结束
		System.out.println("耗时:" + (end - start) + "毫秒");//打印耗时

	}
	
	/**
	* 拦截功能
	*/
	@Override
	public String run(String xxx){
  		//添加拦截逻辑
		if("xxx".equals("...")){
  			//***关键*** 执行真正的逻辑
			service.run(xxx);
		}
		//...
	}

}

优缺点

优点:

  • 可以不修改原目标类(指ServiceImpl)的代码
  • 可以在代理类中对功能进行拦截和扩充

缺点:

  • 代理类需要实现与目标类一样的接口,会导致代理类数量较多,不易维护
  • 一旦接口增加方法,目标类和代理类都需要维护

鉴于这两个缺点,JDK 提供了动态代理

3-动态代理,JDK(基于接口)

为何要引入JDK动态代理

为了解决静态代理的带来的以下问题 -> 引入了JDK动态代理:

  • 代理类需要实现与目标类一样的接口,会导致代理类数量较多,不易维护
  • 一旦接口增加方法,目标类和代理类都需要维护

代码示例

JDK动态代理的实现和静态代理一样,不同的是代理类的创建方式不同:

  • 静态代理是直接新增一个代理类。
  • JDK动态代理需要
    1. 持有目标类对象 - 代理类的通过构造方法传入目标类对象(会),代理类持有目标类对象 (第1步掌握“目标类的实例”,与第2步自动生成代理类无关(因为Proxy.newProxyInstance只需传入clazz,不需要实例),与第3步反射调用方法有关(因为哪怕增加别的逻辑,但核心逻辑依然要调用“目标类的实例”)
    2. 创建代理对象 - 通过JDK的Proxy类, Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法获取代理对象, 和一个"调用处理器InvocationHandler"来实现的,通过Proxy来生成代理类实例,而这个代理实例通过调用处理器InvocationHandler的invoke()接收不同的参数, 反射调用真实对象的方法。
    3. 代理类实现 InvocationHandler 接口,重写invoke(Object proxy, Method method, Object[] args)方法。在此需要反射调用第1步传入的“目标类的实例”method.invoke(“目标类的实例”, args),并在此周围添加更多的逻辑,如过滤器等。

1-实现JDK动态代理的util代码(拷贝到任何项目中,都能直接使用) 

!!!重要的两个类!!!
Proxy类
   Proxy.newProxyInstance 用来生成代理实例
InvocationHandler接口
   要@Override invoke()方法,拦截所有原始类的各种方法,可在其前后增加逻辑

2-实际例子

   

优缺点

优点:见“为何要引入JDK动态代理”

缺点:只能对该类,所实现接口中定义的方法,进行代理。 

详细解析

4-动态代理,CGlib(基于类)

为何要引入CGlib动态代理

为了解决 JDK 的动态代理无法代理不实现接口的类的问题 -> 引入使用 CGLib 的实现动态代理。

CGLib(Code Generator Library)是一个强大的、高性能的代码生成库。底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。动态生成一个目标类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

关键点

  • CGlib动态代理需要
    • maven中添加cglib依赖
    • 不再持有目标类对象 - 构造函数就是默认的空构造函数
    • 创建代理对象 - 使用cglib自带的Enhancer.create(clazz), 参数是目标类的clazz对象。Enhancer类是CGLib中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展,以后会经常看到它。
    • 定义拦截器/处理器 - 在调用目标方法时,CGLib会回调MethodInterceptor(Object obj, Method method, Object[] params, MethodProxy proxy)接口方法拦截,来实现你自己的代理逻辑,类似于JDK中的InvocationHandler接口
      • 参数:Object为由CGLib动态生成的代理类实例,Method为上文中实体类所调用的被代理的方法引用,Object[]为参数值列表,MethodProxy为生成的代理类对方法的代理引用。
      • 返回:从代理实例的方法调用返回的值。
      • 其中,proxy.invokeSuper(obj,arg) 调用代理类实例上的proxy方法的父类方法(即实体类TargetObject中对应的方法)

优缺点

优点:完全不受代理类必须实现接口的限制

缺点:对于final方法,无法进行代理

详细解析

CGLib 动态代理 https://blog.csdn.net/meism5/article/details/90781518

CGLIB(Code Generation Library) 介绍与原理 https://www.runoob.com/w3cnote/cglibcode-generation-library-intro.html

原文地址:https://www.cnblogs.com/frankcui/p/13875709.html