文章目录
- 二、代理模式(Proxy Pattern)
- 1、常见的几种代理模式:
- 2、静态代理
- 3、JDK动态代理
- 4、CGLib代理
- 5、CGLib和JDK动态代理的区别
二、代理模式(Proxy Pattern)
根据B站狂神视频整理:https://www.bilibili.com/video/BV1mc411h719?p=9
参考1:https://blog.csdn.net/kongsanjin/article/details/105419414
参考2:https://www.cnblogs.com/cenyu/p/6289209.html
参考3:https://blog.csdn.net/weixin_36759405/article/details/82770422
代理模式属于结构型模式
代理模式定义:为其他对象提供一种代理,以控制对这个对象的访问。代理对象起到中介作用,可去掉功能或者增加额外功能。
1、常见的几种代理模式:
远程代理,负责与远程JVM通信,以实现本地调用者与远程被调用者之间的正常交互。
虚拟代理,用来代替巨大对象,确保它在需要的时候才被创建。
保护代理,给被调用者提供访问控制,确认调用者的权限。
智能引用代理,比如火车票在各地都有售票处,房屋中介等。
以智能引用代理讲讲代理怎么实现的。有两种实现方式:静态代理和动态代理
2、静态代理
静态代理:代理和被代理对象在代理之前是确定的。它们都实现相同的接口或者继承相同的抽象类。
角色分析:
- 抽象角色:一般使用接口或者抽象类
- 真实角色:被代理的
- 代理角色:代理真角色。代理真角色后会做一些附属操作
- 客户:访问代理对象的人
代码
(1)接口类:Rent.java
//租房。抽象角色
public interface Rent {void rent();
}
(2)房东类:Host.java
//房东 真实角色
public class Host implements Rent{public void rent() {System.out.println("房东要出租房子了");}
}
(3)代理类:Proxy.java
//代理角色。代理房东租房
public class Proxy implements Rent{private Host host;public Proxy(){}public Proxy(Host host){this.host = host;}//租房操作,可以添加一些额外操作public void rent() {seeHouse();//看房host.rent();hetong();//签合同fare();//收中介费}public void seeHouse(){System.out.println("中介带你看房子");}public void hetong(){System.out.println("签订租赁合同");}public void fare(){System.out.println("收中介费");}
}
(4)测试类:Client.java
//租客。访问代理对象的人
public class Client {public static void main(String[] args) {Host host = new Host();//代理房东。可以加一些附属操作Proxy proxy = new Proxy(host);proxy.rent();}
}
静态代理简单总结:
根据上边代码可以看出,优点是在不修改房东类(被代理对象)的情况下,中介(代理对象)可以添加额外操作,比如看房,签合同等;缺点是中介和和房东都实现一样的接口,所以会有很多代理类,致命的是一旦接口中增加方法,实现这个接口的房东和中介都要做出修改。
怎么解决?使用动态代理
动态代理有两类,一是基于接口的JDK动态代理,另外一种是基于类的CGLib代理
3、JDK动态代理
代理对象不用实现接口,是利用JDK的API生成的,动态在内存中构建代理对象(需要我们指定)
代理类所在包:java.lang.reflect.Proxy
newProxyInstance方法
参数:
loader - 类加载器来定义类
interfaces - 代理类实现的接口
h - 调度方法动用的处理函数
上边是jdk-api上的,可能看的不是很懂,下边单独说一些,最好结合下边的JDKProxy类来看。
首先,JDKProxy类要实现InvocationHandler接口,而这个接口只有一个invoke方法,我们要重写这个方法,在里面写上我们的逻辑;
然后怎么生成代理类呢?就需要newProxyInstance方法了,参数详细介绍如下。
//处理代理实例上的方法调用并返回结果。
public Object invoke(Object proxy, //调用该方法的代理实例Method method, //要执行的目标对象的方法Object[] args) //执行方法需要的参数throws Throwable;//返回指定接口的代理类的实例。
public static Object newProxyInstance(ClassLoader loader, //指定当前目标对象使用的类加载器类<?>[] interfaces, //目标对象实现的接口的类型InvocationHandler h //事件处理器
)
代码如下:
接口Rent类和Host类是不变的,然后增加一个JDK动态代理类JDKProxy实现InvocationHandler,最后写测试类。
(1)代理类:JDKProxy.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** jdk动态代理,不需要实现接口,需要指定接口类型* 写法一,好理解,以租房为例的话,targetObject就是Rent接口*/
public class JDKProxy implements InvocationHandler {//需要代理的目标对象。//targetObject就是咱们的Rent接口public Object targetObject;//代理对象目标public void setTargetObject(Object targetObject) {this.targetObject = targetObject;}//生成得到代理类public Object getTargetObject(){/*** 返回代理对象* 参数一:指定当前目标对象使用的类加载器。* 参数二:目标对象实现的接口的类型。* 参数三:事件处理器。这里写的this是代表下边重写的invoke方法*/return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);}/*** 实现InvocationHandler接口,就要重写invoke方法。* proxy,调用该方法的代理实例* method,要执行的目标对象的方法(利用反射的原理)* args,执行某方法需要的参数。*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {seeHouse();//看房//动态代理的本质,就是使用反射机制实现Object result = method.invoke(targetObject,args);hetong();//签合同fare();//收费return result;}public void seeHouse(){System.out.println("中介带你看房子");}public void hetong(){System.out.println("签订租赁合同");}public void fare(){System.out.println("收中介费");}
}
(2)测试类:client2.java
public class client2 {public static void main(String[] args) {//真实角色Host host = new Host();//代理角色,现在没有JDKProxy jdkProxy = new JDKProxy();//通过调用程序处理角色来处理我们要用调用的接口对象jdkProxy.setTargetObject(host);//把要代理的对象传过去Rent proxy = (Rent) jdkProxy.getTargetObject();proxy.rent();}
}
代码2
上边的代码是比较好理解的一种写法,下边的这种写法很简单,直接使用newProxyInstance,在它里面重写InvocationHandler方法(参考2博客中的)。
(1)代理工厂类:ProxyFactory.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** 创建动态代理对象* 动态代理不需要实现接口,但是需要指定接口类型*/
public class ProxyFactory {//目标对象private Object target;public ProxyFactory(Object target) {this.target = target;}//给目标对象生成代理对象public Object getProxyInstance() {return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {seeHouse();//执行目标对象方法Object returnValue = method.invoke(target, args);hetong();fare();return returnValue;}});}public void seeHouse(){System.out.println("中介带你看房子");}public void hetong(){System.out.println("签订租赁合同");}public void fare(){System.out.println("收中介费");}
}
(2)测试类:client3.java
public class client3 {public static void main(String[] args) {//目标对象Rent target = new Host();//给目标对象创建代理对象Rent proxy = (Rent) new ProxyFactory(target).getProxyInstance();//执行方法proxy.rent();}
}
代理对象不用实现接口,但目标对象(房东host)一定要实现接口(Rent),否则不能使用JDK动态代理。
4、CGLib代理
上边的静态代理和JDK动态代理都需要目标对象实现一个接口(Host实现Rent),但有时候,目标对象就是一个对象,没有实现任何接口,就不能使用JDK动态代理。那这时候可以使用CGLib代理。
CGLibb代理也叫子类代理,它是在内存中构建一个子类对象,从而实现对目标对象功能的扩展。
注意:
代理的类不能为final,否则会报错。
如果目标对象的方法为final或static,不会执行额外添加的功能。比如看房,签合同。
代码
需要导入CGLib的jar文件,但spring的核心已包括了CGLib的功能,可以直接导入spring的核心包。
<dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>5.2.9.RELEASE</version>
</dependency>
(1)CGLib代理工厂类:CGLibProxyFactory.java
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** CGLib子类代理工厂:内存中动态构建一个子类对象*/
public class CGLibProxyFactory implements MethodInterceptor {//目标对象private Object target;public CGLibProxyFactory(Object target) {this.target = target;}//给目标对象创建一个代理对象public Object getProxyInstance(){//1、工具类Enhancer en = new Enhancer();//2、设置父类en.setSuperclass(target.getClass());//3、设置回调函数en.setCallback(this);//4、创建子类(代理对象)return en.create();}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {seeHouse();//看房//执行目标对象的方法Object returnValue = method.invoke(target, args);hetong();//签合同fare();//收费return returnValue;}public void seeHouse(){System.out.println("中介带你看房子");}public void hetong(){System.out.println("签订租赁合同");}public void fare(){System.out.println("收中介费");}
}
Enhancer是cglib中使用频率很高的一个类,它是一个字节码增强器,可以用来为无接口的类创建代理。
(2)测试类:client4.java
public class client4 {public static void main(String[] args) {//目标对象Host host = new Host();//生成代理对象Host proxy = (Host) new CGLibProxyFactory(host).getProxyInstance();//执行代理对象的方法proxy.rent();}
}
Spring在选择用JDK还是CGLiB的依据:
(1)当Bean实现接口时,Spring就会用JDK的动态代理
(2)当Bean没有实现接口时,Spring使用CGlib是实现
(3)可以强制使用CGlib,只需要在spring配置中加入
<aop:aspectj-autoproxy proxy-target-class="true"/>
5、CGLib和JDK动态代理的区别
(1)JDK动态代理是实现了被代理对象的接口,CGLib是继承了被代理对象。
(2)JDK和CGLib都是在运行期生成字节码,JDK是直接写Class字节码,CGLib使用ASM框架写Class字节码,CGLib代理实现更复杂,生成代理类比JDK效率低。
(3)JDK调用代理方法,是通过反射机制调用,CGLib是通过FastClass机制直接调用方法,CGLib执行效率更高。