文章目录
- 代理模式
- 1、使用代理模式的作用
- 2、实现代理的方式
- 3、静态代理
- 4、动态代理
- 4.1、JDK动态代理
- 4.1.1、什么是JDK动态代理
- 4.1.2、jdk动态代理的实现
- 4.2、CGLIB代理
- 5、静态代理和动态代理的对比
代理模式
代理模式是指,为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户类和目标对象之间起到中介的作用。
换句话说,使用代理对象,是为了在不修改目标对象的基础上,增强主业务逻辑。
客户类真正想要访问的对象是目标对象,但客户类真正可以访问的对象是代理对象。客户类对目标对象的访问是通过访问代理对象来实现的。代理类与目标类要实现同一个接口。
例如:有A,B,C三个类,A原来可以调用C类的方法,现在因为某种原因C类不允许A类调用其方法,但B类可以调用C类的方法,A类通过B类调用C类的方法。这里B是C的代理,A通过代理B访问C。
原来的访问关系:
通过代理的访问关系:
比如Windows系统的快捷方式也是一种代理模式。快捷方式代理的是真实的程序,双击快捷方式是启动它代表的程序。
1、使用代理模式的作用
①、功能增强:在原有的功能上,增加了额外的功能。新增加的功能,叫做功能增强。
②、控制访问:代理类不让你访问目标,例如商家不让用户访问厂家类似。
2、实现代理的方式
静态代理和动态代理。
3、静态代理
静态代理是指,代理类在程序运行前就已经定义好.java源文件,其与目标类的关系在程序运行前就已经确立。在程序运行前代理类已经编译为.class文件。
静态代理实现步骤:
(1)、定义一个接口及其实现类;
(2)、创建一个代理类同时实现这个接口;
(3)、将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。
特点:实现简单、容易理解。
模拟商家在TaoBao和JingDong购买笔记本电脑的例子:
定义业务接口
//定义业务接口 LaptopSell(目标接口)
public interface LaptopSell {
//抽象方法sell, sell是目标方法
float sell(int amount);
}
定义接口实现类:
//LenovoLaptopFactory目标类,该类实现了业务接口
public class LenovoLaptopFactory implements LaptopSell {
@Override
public float sell(int amount) {
return 5550.0f;
}
}
代理商TaoBao
public class TaoBao implements LaptopSell {
//声明商家代理的厂家是哪个
private LenovoLaptopFactory factory = new LenovoLaptopFactory();
@Override
public float sell(int amount) {
//向厂家发送顶订单告诉厂家我买了电脑,厂家发货
//调用目标类的方法
float price = factory.sell(amount); //厂家的价格
//商家,需要加价,也就是代理要增加价格
price += 100; //增强功能,代理类在完成目标类方法调用后增强了功能
return price;
}
}
代理商JingDong:
public class JingDong implements LaptopSell {
private LenovoLaptopFactory factory = new LenovoLaptopFactory();
@Override
public float sell(int amount) {
//调用目标类中的方法
float price = factory.sell(amount);
price += 90;
return price;
}
}
客户端调用者,购买商品类:
public class ShopMain {
public static void main(String[] args) {
float price = 0.0f;
TaoBao taoBao = new TaoBao();
price = taoBao.sell(1);
System.out.println(price);
System.out.println("============");
JingDong jingDong = new JingDong();
price = jingDong.sell(1);
System.out.println(price);
}
}
图示:
静态代理的缺点:
1)、代码复杂,难于管理
代理类和目标类实现了相同的接口,每个代理都需要实现目标类的方法,这样就出现了大量的代码重复。如果接口增加一个方法,除了所有目标类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
2)代理类依赖目标类,代理类过多
代理类只服务于一种类型的目标类,如果要服务多个类型。势必要为每一种目标类都进行代理,静态代理在程序规模稍大时就无法胜任了,代理类数量过多。
4、动态代理
动态代理是指代理类对象在程序运行时由JVM根据反射机制动态生成的。动态代理不需要定义代理类的Java源文件。
动态代理其实就是jdk运行期间,动态创建class字节码并加载到JVM。
动态代理的实现方式有两种:
1、使用JDK动态代理;
2、通过CGLIB动态代理;
4.1、JDK动态代理
4.1.1、什么是JDK动态代理
使用java反射包中的类和接口实现动态代理的功能。
JDK的动态代理要求目标对象必须实现接口
反射包 java.lang.reflect , 里面有三个类 : InvocationHandler , Method, Proxy.
一、InvocationHandler 接口(调用处理器):
就一个方法invoke(),
invoke():表示代理对象要执行的功能代码。你的代理类要完成的功能就写在invoke()方法中。
代理类完成的功能:
1、 调用目标方法,执行目标方法的功能
2、 功能增强,在目标方法调用时,增加功能。
方法原型:
参数: Object proxy: jdk创建的代理对象,无需赋值。
Method method: 目标类中的方法,jdk提供method对象的
Object[] args:目标类中方法的参数, jdk提供的。
public Object invoke(Object proxy, Method method, Object[] args)
InvocationHandler 接口:表示你的代理要干什么。
怎么用:
1.创建类实现接口InvocationHandler;
2.重写invoke()方法, 把原来静态代理中代理类要完成的功能,写在这。
二、Method类:
表示方法的, 确切的说就是目标类中的方法。
作用:通过Method可以执行某个目标类的方法,Method.invoke();
method.invoke(目标对象,方法的参数)
说明: method.invoke()就是用来执行目标方法的,等同于静态代理中的
向厂家发送订单,告诉厂家,我买了Laptop,厂家发货
float price = factory.sell(amount);
三、Proxy类:
核心的对象,创建代理对象。之前创建对象都是 new 类的构造方法()。
现在我们是使用Proxy类的方法,代替new的使用。
方法: 静态方法 newProxyInstance()
作用是: 创建代理对象, 等同于静态代理中的TaoBao taoBao = new TaoBao();
参数:
- ClassLoader loader 类加载器,负责向内存中加载对象的。 使用反射获取对象的ClassLoader
类a , a.getCalss().getClassLoader(), 目标对象的类加载器 - Class<?>[] interfaces: 接口, 目标对象实现的接口,也是反射获取的。
- InvocationHandler h : 我们自己写的,代理类要完成的功能。
返回值:就是代理对象
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
4.1.2、jdk动态代理的实现
实现步骤:
1、创建接口,定义目标类要完成的功能
2、创建目标类实现接口
3、创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能
①、调用目标方法;
②、增强功能。
4、使用Proxy类的静态方法,创建代理对象。并把返回值转为接口类型。
定义目标接口:
public interface LaptopSell {
float sell(int amount);
}
定义目标接口实现类:
public class LenovoLaptopFactory implements LaptopSell {
@Override
public float sell(int amount) {
return 5550.0f;
}
}
定义调用处理程序:
public class MySellHandler implements InvocationHandler {
private Object target = null;
public MySellHandler() {}
//使用构造方法传入目标对象,给目标对象完成代理功能
public MySellHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//执行目标方法
Object ret = method.invoke(target,args);
float price = (float) ret;
//在目标方法执行完后增强功能
ret = price + 100;
return ret;
}
}
创建动态代理对象:
public class MainShop {
public static void main(String[] args) {
//创建目标对象
LaptopSell factory = new LenovoLaptopFactory();
//创建InvocationHandler对象
InvocationHandler handler = new MySellHandler(factory);
//创建代理对象
LaptopSell proxy = (LaptopSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(),
factory.getClass().getInterfaces(),handler);
//通过代理对象执行目标方法
System.out.println(proxy.sell(1));
}
}
图示:
类图:
4.2、CGLIB代理
CGLIB (opens new window)(Code Generation Library)是一个基于ASM (opens new window)的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB (opens new window), 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
JDK 动态代理和 CGLIB 动态代理对比:
1、JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
2、就二者的效率来说,大部分情况都是 JDK 动态代理更优秀。
5、静态代理和动态代理的对比
①、动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,非常麻烦。
②、静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。