一、代理设计模式
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
意思就是不想直接暴露目标对象,而提供个拦截缓冲。日常开发中,会把对目标对象的请求都重定向于代理对象,代理对象会进行方法的加强,例如进行调用前后的一些操作,Spring AOP就是其中一个应用的案例。
上图就是代理模式的结构图,一般会设置一个接口类,目标类和代理类都实现同一套接口,代理类会持有目标类的实例。客户端调用代理类的方法,从而间接调用目标类的方法。为了方便,直接上代码:
1、接口类
2、目标类
3、代理类
package proxy;
public class StaticProxyObjet implements Myinterface{
TargetObjet targetObjet;
public StaticProxyObjet(TargetObjet targetObjet){
this.targetObjet=targetObjet;
};
@Override
public void doSomething() {
beforInvoke("doSomething");
targetObjet.doSomething();
afterInvoke("doSomething");
}
@Override
public void somethingElse(String arg) {
beforInvoke("somethingElse");
targetObjet.somethingElse(arg);
afterInvoke("somethingElse");
}
private void beforInvoke(String arg){
System.out.println("调用方法"+arg+"前");
};
private void afterInvoke(String arg){
System.out.println("调用方法"+arg+"后");
};
}
4、测试类
package proxy;
public class StaticProxyTest {
public static void main(String[] args) {
StaticProxyObjet proxy = new StaticProxyObjet(new TargetObjet());
proxy.doSomething();
proxy.somethingElse("20220601");
}
}
输出结果:
常规设计模式中的代理就是上述的静态代理,
其优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性
其主要缺点是:
- 代理模式会造成系统设计中类的数量增加
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;
有没办法,既保留代理的优点的同时,对其缺点解决呢?答案是肯定的,那就是动态代理,常见的动态代理有两种方式,jdk动态代理以及cglib动态代理。
二、jdk动态代理
与静态代理方式类似类似,都需要提供一个接口类,然后目标类实现该接口。不同的地方是不需要在直接编写代理类,而是编写实现InvocationHandler 接口的处理器。
对目标对象的调用请求都会被重定位到这个处理器上,而且由InvocationHandler这个接口的 invoke 方法来进行调用。
1、处理器类
留意,method.invoke(target,args)中首个参数,使用的并非方法this.invoke()里的proxy对象,而是构造器赋值的目标对象。
package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicProxyHandler implements InvocationHandler {
public Object target;
public DynamicProxyHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy:"+proxy.getClass()+" method:"+ method +" args:"+args);
return method.invoke(target,args);
}
}
2、测试类
package proxy;
import java.lang.reflect.Proxy;
public class DynamicProxyTest {
public static void main(String[] args) {
Myinterface proxy = (Myinterface) Proxy.newProxyInstance(Myinterface.class.getClassLoader(),new Class[]{Myinterface.class},new DynamicProxyHandler(new TargetObjet()));
proxy.doSomething();
proxy.somethingElse("2022年六一儿童节");
}
}
输出结果:
jdk的优点就是不需要编写实现统一接口的代理类,动态代理类只会在运行期间存在于内存。不过对于没有实现接口的目标类来说,就不能使用这种方式。因为$Proxy0是已经继承了Proxy这个父类,根据jjava的单继承特性,它就不能再继承其他父类。
gclib就是针对没有实现接口的类实现动态代理。
三、CGLib实现动态代理
GCLib是对底层的asm字节码技术进行调用,可以对为一个类创建子类,该子类设置方法拦截器,对父类方法的调用都会被子类拦截并进行处理。具体实现流程如下:
1、编写目标类
package proxy;
public class CglibTarget {
public void say(){
System.out.println("hello CglibTarget!");
}
}
2、编写拦截器
package proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibTargetProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz){
// 设置代理目标
enhancer.setSuperclass(clazz);
// 设置单一回调对象,在调用中拦截对目标方法的调用
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("事务启动...");
methodProxy.invokeSuper(o, objects);
System.out.println("关闭事务");
return methodProxy;
}
}
该拦截器需要实现MethodInterceptor,并创建Enhancer增强器(为无接口的类创建代理)。它的功能与java自带的Proxy类挺相似的。它会根据某个给定的类创建子类,生成的所有非final的方法都带有回调钩子。
3、测试类
package proxy;
public class CglibProxyTest {
public static void main(String[] args) {
CglibTargetProxy proxy = new CglibTargetProxy();
CglibTarget target1 = (CglibTarget) proxy.getProxy(CglibTarget.class);
target1.say();
}
}
运行结果:
四、jdk动态代理和CGLib的比较
实现机制 | 委托机制。目标类和代理类实现统一接口,提供的处理器实现InvocationHandler并持有目标类。代理类委托处理器来掉用目标类的方法。 |
回调方式 | 反射 |
优点 | 无需依赖第三方jar包 |
缺点 | 1、只能代理实现接口的类 2、速度较慢 |
适用场景 | 目标对象实现了接口 |
效率瓶颈 | 反射调用的速度稍慢 |
实现机制 | 继承机制。代理类采用方法拦截的技术拦截所有父类方法的调用并顺势志入横切逻辑。 |
回调方式 | 通过FastClass方法索引调用 |
优点 | 1、代理对象执行速度较快 2、可代理没有实现接口的类 |
缺点 | 1、不能代理final类 2、生成字节码较慢 |
适用场景 | 1、非接口类 2、非final类 3、不包含final方法 |
效率瓶颈 | 首次调用,需要生存多个子类的Class对象比jdk方式要慢,多次调用则速度优势更明显。 |