一、代理模式概述
1.1、代理模式的理解
参考人家的举例,感觉挺形象,容易理解:
就拿明星与经纪人的关系来说,明星就好比被代理类,明星只需要负责唱歌,表演或给粉丝签名等事务,而类似签合同,面谈,计划日程安排等经纪事务都不需要明星个人去做,可以交给其经纪人来代理完成。
1.2、代理模式的分类:
- 静态代理模式(静态的定义代理类,编译前定义好):涉及接口的使用。
- 动态代理模式(动态的生成代理类):涉及接口、反射、Proxy类的相关api。
1.3、代理模式的应用场景
- 安全代理:对外隐藏真实的调用者
- 远程代理:通过代理类处理远程方法调用(RMI)
- 延迟加载
eg:
意图:为其他对象提供一种代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层。
关键代码:实现与被代理类组合。
应用实例: 1、Windows 里面的快捷方式。2、买火车票不一定在火车站买,也可以去代售点。 3、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。4、spring aop。
优点: 1、职责清晰。 2、高扩展性。 3、智能化。
缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
使用场景:按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。
注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
二、静态代理
2.1、静态代理模式的实现要求
- 提供两个真实的具体的类,代理类与被代理类;
- 两个类同时实现同一个接口,接口中定义多个抽象方法(提取代理类,被代理各自的行为任务);
- 代理类一定要由被代理对象的引用,为了能在代理类中调用被代理类的重写接口中的方法。
2.2、静态代理存在的问题
- 一旦接口新增或者修改,那么代理对象和被代理对象就得去适配修改。
- 静态代理是在代码编写时,去生成的,class文件必然会造成类爆炸的风险。
2.3、静态代理示例
(1)被代理接口:
public interface StarInterface {
//个人签名
void personSignature();
//表演
void show();
//签合同
void signContract();
//日程安排
void schedule();
}
(2)被代理类(被代理类实现被代理接口):
//明星类
public class Star implements StarInterface{
@Override
public void personSignature() {
System.out.println("明星个人签名");
}
@Override
public void show() {
System.out.println("明星表演");
}
@Override
public void signContract() {
}
@Override
public void schedule() {
}
}
(3)代理类(实现被代理接口,有被代理对象的引用):
//经纪人
public class Agent implements StarInterface{
private Star star;
public Agent(Star star) {
this.star = star;
}
public Agent() {
}
@Override
public void personSignature() {
star.personSignature();
}
@Override
public void show() {
star.show();
}
@Override
public void signContract() {
System.out.println("经纪人谈合同");
}
@Override
public void schedule() {
System.out.println("经纪人安排演唱会");
}
}
(4)测试类:
public class StaticProxyTest {
public static void main(String[] args) {
Agent agent = new Agent(new Star());
agent.personSignature();
agent.show();
agent.schedule();
agent.signContract();
}
}
三、动态代理
当我们写了一个接口,里面有方法,然后写了实现类实现方法,完成方法逻辑,然后有一天,想对这个方法进行修改,但是不想改源码,有两种方式可以实现。第一种是静态代理,创建一个类继承实现类,然后对方法进行修改,这样太局限,因为只能针对特定的类增强方法,有100个实现类就要创建100个子类去实现。第二种方式是动态代理,可以动态地生成代理类,这是可以接受的。
动态代理,顾名思义,就是在运行时去动态生成代理,我们下面分别说两种实现方式,分别为JDK 动态代理 和 Cglib 动态代理。
3.1、JDK动态代理
JDK提供了一些API,可以实现动态代理,通过实现 InvocationHandler接口和 Proxy.newProxyInstance来实现。
(1)需要被代理的接口(必须要有)和实现类,采用上述静态代理中的例子,被代理接口StarInterface和被代理类Star。
(2)创建动态代理类
需要实现InvocationHandler接口,重写invoke方法,这里可以对方法进行增强。:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class Dynamicproxy implements InvocationHandler {
private Object targetObject;
public Dynamicproxy(Object targetObject) {
this.targetObject = targetObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("日志开始");
Object invoke = method.invoke(targetObject, args);
System.out.println("日志结束");
return invoke;
}
}
(3)创建动态代理对象
import com.qizekj.proxy.Star;
import com.qizekj.proxy.StarInterface;
import java.lang.reflect.Proxy;
/**
* @author chenqun
* @date 2023/3/10 20:20
*/
public class JDKProxyTest {
public static void main(String[] args) {
//生成代理类文件 在根目录的同级目录,com下
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//实现了接口的业务类
Star iservice = new Star();
//获取Class对象
Class<?> iserviceClass = iservice.getClass();
//代理类 实现需要实现InvocationHandler接口,重写invoke方法 传入业务实现类对象
Dynamicproxy dynamicproxy = new Dynamicproxy(iservice);
//创建代理类对象
StarInterface so = (StarInterface) Proxy.newProxyInstance(iserviceClass.getClassLoader(),
iserviceClass.getInterfaces(), dynamicproxy);
so.show();
}
}
运行结果:
如果这时接口新增或者修改,不再需要去修改代理类。因为其本质是,通过反射+classloader去实现的,所以不依赖于对象。
但是依然有一个问题,就是Proxy.newProxyInstance使用上有一个弊端,就是必须面向接口,如果源对象也就是被代理对象,如果没有实现某个接口,这时就不能用JDK动态代理了,此时 Cglib 可以帮我们解决这个问题。
3.2、Cglib 代理
我们知道一个对象在内存中存储,一般就是在栈或者堆上,那是否有一种方法或者组件,可以直接从内存copy这个对象,实现深克隆,也就是在内存中在copy一个出来。
(1)Cglib代理模式的基本介绍
- JDK动态代理模式要求目标对象实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理,这就是Cglib代理。
- Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展。
- Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口,它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截。
- Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类。
- Cglib由于是基于字节码的,显然这时android就不能使用了,因为android是dex文件,这怎么办?没关系,有dexmaker 和 cglib-for-android 库。
(2)Cglib代理的实现
- 第一步:引入cglib的文件,目标类使用上述静态方法中的Star。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.7</version>
</dependency>
- 第二步:创建方法拦截器
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author chenqun
* @date 2023/3/10 20:33
*/
public class MyInterceptor implements MethodInterceptor {
private Object target;
public MyInterceptor(Object target) {
this.target = target;
}
/**
*
* @param o 代理对象
* @param method 被代理对象的方法
* @param objects 方法入参
* @param methodProxy 代理方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("-----cglib before-----");
// 调用代理类FastClass对象
Object result = methodProxy.invokeSuper(o, objects);
// Object result = methodProxy.invoke(target, objects);
System.out.println("-----cglib after-----");
return result;
}
}
- 第三步,调用测试。
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
/**
* @author chenqun
* @date 2023/3/10 20:37
*/
public class CglibTest {
public static void main(String[] args) {
Star star = new Star();
// 代理类class文件存入本地磁盘方便我们反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./code");
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
enhancer.setSuperclass(Star.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MyInterceptor(star));
// 创建代理对象
Star starService = (Star)enhancer.create();
// 通过代理对象调用目标方法
starService.personSignature();
starService.show();
}
}
运行结果:
3.3、JDK 和 CGLIB动态代理的区别
JDK代理使用的是反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
CGLIB代理使用字节码处理框架asm,对代理对象类的class文件加载进来,通过修改字节码生成子类。
JDK创建代理对象效率较高,执行效率较低;
CGLIB创建代理对象效率较低,执行效率高。
JDK动态代理机制是委托机制,只能对实现接口的类生成代理,通过反射动态实现接口类;
CGLIB则使用的继承机制,针对类实现代理,被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,因为是继承机制,不能代理final修饰的类。
JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,需要满足以下要求:
1.实现InvocationHandler接口,重写invoke()
2.使用Proxy.newProxyInstance()产生代理对象
3.被代理的对象必须要实现接口
CGLib 必须依赖于CGLib的类库,需要满足以下要求:
1.实现MethodInterceptor接口,重写intercept()
2.使用Enhancer对象.create()产生代理对象
总结:
(1)jdk代理只能对实现了接口的类进行代理,而cglib代理可以对普通类进行代理;
(2)jdk代理是通过反射的方式来实现动态代理,而cglib则是通过为目标类生成一个子类的方式来实现动态代理;
(3)由于cglib代理是为目标类生成了一个子类,并对父类方法进行增强,所以目标类不能用final修饰;
3.4、什么情况下使用JDK或CGLIB代理
(1)如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP,可以强制使用CGLIB实现AOP。
(2)如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换。
3.5、强制使用CGLIB实现AOP的方法
(1)添加CGLIB库(aspectjrt-xxx.jar、aspectjweaver-xxx.jar、cglib-nodep-xxx.jar)
(2)在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>