是什么?
如果因为某些原因访问对象不适合,或者不能直接引用目标对象,这个时候就需要给该对象提供一个代理以控制对该对象的访问,代理对象作为访问对象和目标对象之间的中介;
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理,静态代理代理类在编译时期就生成,而动态代理代理类则是在Java运行时动态生成的,而动态代理又分为JDK动态代理和CGLIB动态代理两种;
结构
抽象主题类:通过接口或抽象类声明真实主题和代理对象实现的业务方法;
真实主题类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象;
代理类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或者扩展真实主题的功能;
实现
我们现在实现一个租客通过中介买房的例子;
静态代理
抽象主题类
public interface Retending {
void retend();
}
具体主题类
public class TomsonYipin implements Retending{
//汤臣一品房东
@Override
public void retend() {
System.out.println("出售汤臣一品");
}
}
代理类
public class AgencyProxy implements Retending{
//房产中介
private final TomsonYipin tomsonYipin=new TomsonYipin(); //聚合真实主题类
@Override
public void retend() {
System.out.println("收取中介费用");
tomsonYipin.retend();
}
}
租客
public class Tenant {
public static void main(String[] args) {
//模拟租客向中介租房
//直接访问对象不能是房东,而是中介;
AgencyProxy proxy = new AgencyProxy();
proxy.retend();
}
}
上面的代码AgencyProxy类其实就是就是起到一个中介的角色,将具体主题类和用户类相互隔离开,同时也对具体主题的方法进行了一点增强;
JDK动态代理
Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法:newProxyInstance方法来获取代理对象;
抽象主题类
public interface Retending {
void retend();
}
具体主题类
public class TomsonYipin implements Retending {
//汤臣一品房东
@Override
public void retend() {
System.out.println("出售汤臣一品");
}
}
代理工厂获取代理对象
public class AgencyProxyFactory {
private final TomsonYipin tomsonYipin = new TomsonYipin();
//创建代理对象并返回
public Retending getObjectProxy(){
Retending retending = (Retending)Proxy.newProxyInstance(tomsonYipin.getClass().getClassLoader(),
tomsonYipin.getClass().getInterfaces(),
new InvocationHandler() {
/**
* Object proxy:它其实就是我们外面获取到的retending对象,基本不用
* Method method:对接口中的方法进行封装的method对象;
* Object[]args:调用方法的实际参数
* return:就是抽象主题定义的方法的返回值
* */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("中介收取服务费");
//执行目标对象的方法=>通过反射
Object obj = method.invoke(tomsonYipin, args);
return obj;
}
});
return retending;
}
}
租客
public class Tenant {
public static void main(String[] args) {
//模拟租客向中介租房
AgencyProxyFactory proxyFactory = new AgencyProxyFactory();
Retending proxy = proxyFactory.getObjectProxy();
proxy.retend();
}
}
分析
1.首先我们没有自己再去构造代理类了,而是调用Proxy中的newProxyInstance方法来帮我们创建代理对象
2.其中我们提供类加载器,类所实现的接口,以及一个InvocationHandler接口对象;
我们new出该接口对象,需要以匿名内部类的形式去实现该接口中的invoke方法;
这个方法非常关键,当用户在外面获取到代理对象之后,调用对象中的方法时,其实就是相当于调用的这个invoke方法,也就是说用户在外面调用retending.retend()方法的时候,其实就是在调用该invoke方法;
3.而我们现在在invoke方法中去调用具体主题对象中的方法,就只需要使用method中的invoke方法即可,因为这个method对象就是对接口中的方法进行了封装;
思考(重点)
我们写的这个AgencyProxyFactory是代理类吗?
这个类并不是代理模式中所说的代理类,它只是我们写的一个工厂类,我们在这个工厂类中获取的是代理对象,而代理类是程序帮我们在运行过程中动态的在内存中生成的类;
在代理类$Proxy0中它实现了Retending接口,这也就印证了我们之前说的真实类和代理类实现同样的接口;
代理类$Proxy0将我们提供了的匿名内部类对象传递给了父类;
执行流程
1.在测试类中我们通过代理工厂去获取了代理对象,并调用它的retend()方法;
2.根据多态的特性,实际执行的是代理类$Proxy0中的retend()方法;
3.代理类$Proxy0中的retend()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法(也就是我自己new的那个InvocationHandler对象,在匿名内部类中实现的invoke方法);
4.invoke方法通过反射执行了真实对象所属类中的retend()方法;
CGLIB动态代理
同样是上面的案例,但这次我们没有定义Retending接口,而只有TomsonYipin类(或者是以继承的方式),那这个时候显然就不能使用JDK动态代理了,因为JDK动态代理要求必须定义接口,对接口进行代理;
CGLIB是一个功能强大,高性能的代码生成包,它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充;
添加依赖
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
</dependencies>
具体主题类
public class TomsonYipin {
//汤臣一品房东
public void retend() {
System.out.println("出售汤臣一品");
}
}
代理工厂获取代理对象
public class CGLIBProxyFactory implements MethodInterceptor {
//声明目标对象
private final TomsonYipin tomsonYipin = new TomsonYipin();
//CGLIB代理工厂用来获取代理对象
public TomsonYipin getProxy(){
//创建Enhancer对象,它类似于JDK动态代理中的Proxy类
Enhancer enhancer = new Enhancer();
//设置父类的字节码对象
enhancer.setSuperclass(TomsonYipin.class);
//设置回调函数
enhancer.setCallback(this);
//创建代理对象
TomsonYipin proxy = (TomsonYipin) enhancer.create();
return proxy;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//调用目标对象的方法
System.out.println("CGLIB中介收取服务费");
Object obj = method.invoke(tomsonYipin, objects);
return obj;
}
}
租客
public class Tenant {
public static void main(String[] args) {
CGLIBProxyFactory proxyFactory = new CGLIBProxyFactory();
TomsonYipin proxy = proxyFactory.getProxy();
System.out.println(proxy.getClass());
proxy.retend();
}
}
分析
1.声明目标对象
2.创建Enhancer对象(对标JDK动态代理中的Proxy类)
3.设置父类的字节码对象,指定父类;
4.设置回调函数(实现了MethodInterceptor接口的类,也就是当前类)
该接口类似于JDK动态代理中的InvocationHandler,因此实现的intercept方法其实就是我们调用代理对象中的方法时实际执行的方法;
5.调用enhancer中的create方法创建代理对象并返回
对比
动态代理和静态代理
动态代理与静态代理相比较,最大的好处是接口中声明的方法都被转移到调用处理器一个集中的方法处理(也就是InvocationHandler中的invoke方法),这样在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转;
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度,而动态代理不会出现该问题;
JDK动态代理和CGLIB动态代理
使用CGLIB实现动态代理,CGLIB底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高,唯一需要注意的是;CGLIB不能对声明为final的类或者方法进行代理,因为CGLIB原理是动态生成被代理类的子类;
在JDK1.6、1.7、1.8逐步对JDK动态代理优化后,在调用次数较少的情况下,JDK代理效率要高于CGLIB代理效率,只有当进行大量调用的时候,JDK1.6和1.7比CGLIB代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLIB代理,所以如果有接口则使用JDK动态代理,如果没有接口则使用CGLIB动态代理;
优缺点
优点
1.代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
2.代理对象可以扩展目标对象的功能;
3.代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
缺点
增加了系统的复杂度;
使用场景
1.远程代理
本地服务通过网络请求远程服务,为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常,为良好的代码设计和可维护性
2.防火墙代理
当我们将浏览器配置成使用代理功能时,防火墙就将我们的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给我们的浏览器;
3.保护代理
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限;