动态代理和静态代理都是代理.那什么是代理模式呢?
代理模式是指,为其他对象提供一种代理以控制这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户类和目标对象之间起到中介的作用.
换句话说,使用代理对象,是为了在不修改目标对象的基础上,增强主业务逻辑(即代理对象可以在目标对象的基础上增加一些新的功能).
客户端真正想要访问的对象是目标对象,但是客户类真正可以访问的对象是代理对象,客户类对目标对象的访问是通过访问代理来实现的.当然,代理类和目标类要实现同一个接口.
例如: 有A、B、C三个类,A可以调用C类的方法,现在因为某种原因C类不允许A类调用其方法,但是B类可以调用C类的方法.A类通过B类调用C类的方法.这里B是C的代理.A通过代理B访问C.
介绍完了代理模式,那么使用代理模式的作用是什么呢?
- 目标类方法的调用
- 功能增强(主要作用): 在目标对象原有功能基础上,代理对象增加一些额外的功能.
- 控制访问: 代理类不让客户端访问目标
实现代理的方式
1. 静态代理
- 代理类是自己手工实现的,即自己创建一个java类,表示代理类.
- 要代理的目标类是确定的
/**
* 卖U盘接口
*/
public interface UsbSell {
/**
* @param amount 表示卖U盘的数量
* @return
*/
float sell(int amount);
}
/**
* 代理模式中的目标类:金士顿厂家,卖U盘,不接受用户的直接购买。
*/
public class UsbKingFactory implements UsbSell{
/**
* U盘的价格是85原,购买的数量是amount,返回总价格
* @param amount 表示卖U盘的数量
* @return
*/
public float sell(int amount) {
return 85f * amount;
}
}
/**
* 代理类: 京东是一个商家,代理金士顿U盘的销售
*/
public class Jingdong implements UsbSell{
UsbSell factory = new UsbKingFactory();
public float sell(int amount) {
// 金士顿厂家销售U盘的价格
float kingPrice = factory.sell(amount);
// 京东作为一个卖家要加价,以获取利润。这部分即代理中的功能增强
float profit = 10f * amount;
// 返回京东的售价
return kingPrice + profit;
}
}
/**
* 代理模式中的客户端
*/
public class Shopping {
public static void main(String[] args) {
UsbSell jingdong = new Jingdong();
// 输出用户买10个U盘的价格
System.out.println(jingdong.sell(10));
}
}
静态代理的优缺点:
优点:
实现简单、容易理解
缺点:
- 当目标类增加了,代理类也需要成倍的增加,代理类数量过多.比如当增加一个目标类,并且有n个代理类代理该目标类时,那么代理类就要增加n个.
- 当接口中功能增加或修改了,所有实现该接口的目标类和代理类都需要修改.
总结: 静态代理适合比较简单的环境中,接口功能简单、基本不需要变动,并且目标类和代理类数量少.
2. 动态代理
在静态代理中目标类很多时,可以使用动态代理,避免静态代理的缺点.在动态代理中即使目标类很多,1)代理类数量可以很少, 2)修改了接口中的方法时,不会影响代理类.
动态代理的定义
在程序执行过程中,使用jdk的反射机制,创建代理类对象,并动态的指定要代理的目标类.而且不用创建代理类文件.
实现代理的方式
- jdk动态代理
使用java反射包中的类和接口实现动态代理的功能.
反射包java.lang.reflect,里面有三个类: InvocationHandler、Method、Proxy.
限制: 使用JDK的Proxy实现代理,要求目标类和代理类(不需要创建)实现相同的接口,若不存在接口,则无法使用该方式实现(可以使用cglib动态代理实现).
// 使用反射的示例
// 接口
public interface HelloService {
void sayHello(String name);
}
// 目标类1
public class HelloServiceImpl implements HelloService{
public void sayHello(String name) {
System.out.println("你好," + name);
}
}
// 目标类2
public class HelloServiceImpl2 implements HelloService{
public void sayHello(String name) {
System.out.println("hello," + name);
}
}
// 代理模式中的客户端
public class TestApp {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
HelloService helloService = new HelloServiceImpl();
// 即使增加目标类,代理类仍然不需要,通过反射即可解决
// HelloService helloService = new HelloServiceImpl2();
// 获得HelloService的class对象的三种方式:类的实例对象.getClass、类名.class、Class.forName(类的全路径)
// Class c = helloService.getClass();
// Class c = HelloService.class;
Class c = Class.forName("com.kenai.dongtai_proxy.HelloService");
// 通过HelloService的class对象获得sayHello方法
Method sayHello = c.getMethod("sayHello", String.class);
// 执行helloService对象的sayHello方法,参数为"张三"
sayHello.invoke(helloService, "张三");
}
}
JDK动态代理的实现
依赖于反射包java.lang.reflect中的三个类: InvocationHandler、Method、Proxy.
InvocationHandler(调用处理器): 就一个方法invoke()
invoke(): 表示代理对象要执行的功能代码.你的代理类要完成的功能就写在invoke方法中.
// Obejct proxy: jdk创建的代理对象,无需赋值
// Method method: 目标类中的方法
// Object[] args: 目标类中的参数
public Object invoke(Object proxy, Method method, Object[] args);
Method类: 目标类中的方法
作用: 通过Method可以执行某个目标类中的方法.method.invoke().
Method method; method.invoke();
注: method.invoke()方法和InvocationHandler中的invoke()方法只是重名而已.
// 执行目标对象的method方法,参数为下面自己设置的方法参数
method.invoke("目标对象", "方法参数");
Proxy类:核心的对象,用于创建代理对象.之前创建对象都是new类的构造方法,现在使用proxy类的方法代替new的使用.
方法: 静态方法newProxyInstance()
// ClassLoader: 类加载器.负责将class二进制文件加载到JVM的方法区中.获取类加载器的方法可以参考上面反射代码示例中获取class对象的三种方法,在获取到class对象后,class对象.getClassLoader()即可获得.
// interfaces: 目标对象所实现的接口.也是通过反射获取的
// InvocationHandler: 自己写的代理类要完成的功能
// 返回值就是代理对象
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
JDK动态代理实现的步骤:
- 创建接口,定义目标类要完成的功能
- 创建目标类,实现创建的接口
- 创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能: 调用目标方法、功能增强
- 使用Proxy类的静态方法,创建代理对象.并把返回值转为接口类型
动态代理代码
/**
* 卖U盘接口
*/
public interface UsbSell {
/**
* @param amount 表示卖U盘的数量
* @return
*/
float sell(int amount);
}
/**
* 代理模式中的目标类:金士顿厂家,卖U盘,不接受用户的直接购买。
*/
public class UsbKingFactory implements UsbSell {
/**
* U盘的价格是85原,购买的数量是amount,返回总价格
* @param amount 表示卖U盘的数量
* @return
*/
public float sell(int amount) {
return 85f * amount;
}
}
public class MyShellHandler implements InvocationHandler {
private Object target;
public MyShellHandler(Object target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = method.invoke(target,args);
if(method.getName().equals("sell") && res != null){
Float price = (Float)res;
price += (Integer)args[0] * 10;
res = price;
}
return res;
}
}
public class MainShopping {
public static void main(String[] args) {
// 1.创建目标对象
UsbSell factory = new UsbKingFactory();
// 2.创建InvocationHandler对象(方法的调用和功能增强)
InvocationHandler handler = new MyShellHandler(factory);
// 3.创建代理对象
UsbSell proxy = (UsbSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(), factory.getClass().getInterfaces(), handler);
// 4.通过代理执行方法
// 这的sell方法对应InvocationHandler实现类MyShellHandler中的invoke方法中的method,sell方法的参数对应invoke方法中的args
float price = proxy.sell(10);
System.out.println("通过动态代理对象调用方法,总价格为:" + price);
}
}
执行结果:
通过动态代理对象调用方法,总价格为:950.0
- cglib动态代理
cglib是第三方的工具库,可以用来创建代理对象.
原理: 通过继承目标类,创建它的子类,在子类中重写父类的方法,实现功能的修改增强.
限制: 由于cglib是通过继承的方式重写方法,所以目标类不能是final的,方法也不能是final的.
因为被final修饰类不能被继承,被final修饰的方法不能被重写,被final修饰的变量不能被修改.
优点: cglib通过继承的方式重写方法,要求的目标类比较宽松(只要能继承能重写即可).对于无接口的类也能为其创建动态代理.并且代理效率比jdk动态代理的方式快.
应用: cglib在很多框架中使用,比如spring aop、mybatis、Hibernate等.
代理在日常开发中的作用:
当调用其他部门或者公司的人写好的功能时,发现这个功能存在某些缺点,满足不了自己的需要,但是又由于时其他部门或者公司的人写的,不可能获得源代码,便可以通过代理的方式,增加自己的功能代码,而不用改原文件.