1. 动态代理介绍
- 什么是动态代理
- 简单来说就是使用反射动态创建代理对象,使用代理对象来代替目标对象,因此达到增强目标对象的功能
- 动态代理的特点
- 字节码随用随创建,随用随加载。它与静态代理的区别也在于此,因为静态代理是字节码一上来就创建好,并完成加载。装饰者模式就是静态代理的一种体现。
- 动态代理的常用两种方式
2. 基于接口的动态代理
- 在这里我们使用厂家的例子:以前厂家不仅要对产品进行售后处理,还得负责销售的环节,把产品卖给客户。但是随着时间的推移,出现了一个新的角色:代理商,这时候厂家只需要负责对产品的售后处理,销售的事情交给代理商即可。如果客户需要售后维修,那么只需要将产品交给代理商,由代理商送往厂家进行维修。
- 下面我们用代码来演示这种情况。
- 基于接口的动态代理是使用 JDK 官方提供的
Proxy
类,要求被代理类至少实现一个接口 - 定义接口,表示厂家需要具备的功能(销售以及售后)
public interface IProducer {
String sellProduct(double money);
String afterService(double money);
}
public class Producer implements IProducer {
@Override
public String sellProduct(double money) {
return "厂家销售产品,拿到 " + money + "元...";
}
@Override
public String afterService(double money) {
return "厂家提供售后服务,拿到 " + money + "元...";
}
}
public class Client {
public static void main(String[] args) {
final IProducer producer = new Producer();
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Double money = (Double) args[0];
String returnValue;
if ("sellProduct".equals(method.getName())) {
System.out.println("消费者花了 " + money + "元购买商品...");
money = money * 0.8;
returnValue = (String) method.invoke(producer, money);
return returnValue + "代理商抽取 2 成利润...";
} else {
System.out.println("消费者花了 " + money + "元进行售后服务...");
returnValue = (String) method.invoke(producer, args);
return returnValue + "代理商没有抽取利润...";
}
}
});
System.out.println(proxyProducer.sellProduct(10000.0));
System.out.println("-----------------------------");
System.out.println(proxyProducer.afterService(2000.0));
}
}
- 使用
Proxy.newProxyInstance()
创建代理对象,参数如下:
ClassLoader
: 类加载器,用于加载代理对象字节码,和被代理对象使用相同的类加载器。固定写法:被代理对象.getClass().getClassLoader()
Class[]
: 字节码数组,用于让代理对象和被代理对象具有相同的接口方法。固定写法:被代理对象.getClass().getInterfaces()
InvocationHandler
: 处理器,用于提供增强的代码,一般都是编写一个该接口的匿名内部类
invoke()
方法用于拦截被代理对象的方法,执行被代理对象的任何方法都会被该方法拦截到,参数如下:
Object proxy
:代理对象的引用Method method
:当前执行的方法Object[] args
:当前执行方法所需的参数- 返回值类型与被代理对象方法一致
- 匿名内部类访问外部方法的成员变量时都要求外部成员变量添加
final
修饰符,final
修饰变量代表该变量只能被初始化一次,以后不能被修改。参考链接:
- 运行结果如图
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/b36f3bcf4b86e9b879d1cd985a501819.png)
3. 基于子类的动态代理
- 还是使用上面的例子
- 基于子类的动态代理是使用第三方库
CGLIB
提供的 Enhancer
类,要求被代理类不能是被 final
修饰的类(最终类) - 导入第三方库依赖 CGLIB 或者手动导入jar 包
cglib-2.1.3.jar
和 asm.jar
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
public class Producer {
public String sellProduct(double money) {
return "厂家销售产品,拿到 " + money + "元...";
}
public String afterService(double money) {
return "厂家提供售后服务,拿到 " + money + "元...";
}
}
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
}
});
System.out.println(cglibProducer.sellProduct(10000.0));
System.out.println("-----------------------------");
System.out.println(cglibProducer.afterService(2000.0));
}
}
- 使用
Enhancer.create()
创建代理对象,参数如下:
Class
: 字节码,用于指定被代理对象的字节码。固定写法:被代理对象.getClass()
Callback
: 回调接口,用于提供增强的代码,一般都是编写该接口的子接口实现类 MethodInterceptor
intercept()
方法用于拦截被代理对象的方法,执行被代理对象的任何方法都会被该方法拦截到,参数如下:
Object proxy
:代理对象的引用Method method
:当前执行的方法Object[] args
:当前执行方法所需的参数MethodProxy methodProxy
:当前执行方法的代理对象- 返回值类型与被代理对象方法一致