根据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
代理模式属于结构型模式
代理模式定义:为其他对象提供一种代理,以控制对这个对象的访问。代理对象起到中介作用,可去掉功能或者增加额外功能。
远程代理,负责与远程JVM通信,以实现本地调用者与远程被调用者之间的正常交互。
虚拟代理,用来代替巨大对象,确保它在需要的时候才被创建。
保护代理,给被调用者提供访问控制,确认调用者的权限。
智能引用代理,比如火车票在各地都有售票处,房屋中介等。
以智能引用代理讲讲代理怎么实现的。有两种实现方式:静态代理和动态代理
静态代理:代理和被代理对象在代理之前是确定的。它们都实现相同的接口或者继承相同的抽象类。
角色分析:
代码
(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代理
代理对象不用实现接口,是利用JDK的API生成的,动态在内存中构建代理对象(需要我们指定)
代理类所在包:java.lang.reflect.Proxy
参数:
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,执行某方法需要的参数。
*/
@Override
public 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() {
@Override
public 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动态代理。
上边的静态代理和JDK动态代理都需要目标对象实现一个接口(Host实现Rent),但有时候,目标对象就是一个对象,没有实现任何接口,就不能使用JDK动态代理。那这时候可以使用CGLib代理。
CGLibb代理也叫子类代理,它是在内存中构建一个子类对象,从而实现对目标对象功能的扩展。
注意:
代理的类不能为final,否则会报错。
如果目标对象的方法为final或static,不会执行额外添加的功能。比如看房,签合同。
代码
需要导入CGLib的jar文件,但spring的核心已包括了CGLib的功能,可以直接导入spring的核心包。
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>5.2.9.RELEASEversion>
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();
}
@Override
public 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"/>
(1)JDK动态代理是实现了被代理对象的接口,CGLib是继承了被代理对象。
(2)JDK和CGLib都是在运行期生成字节码,JDK是直接写Class字节码,CGLib使用ASM框架写Class字节码,CGLib代理实现更复杂,生成代理类比JDK效率低。
(3)JDK调用代理方法,是通过反射机制调用,CGLib是通过FastClass机制直接调用方法,CGLib执行效率更高。