设计模式-05.代理模式
代理模式
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能,指为其他对象提供一种代理,以控制对这个对象的访问。
代理对象在客户端和目标对象之间起到中介作用。
属于结构型模式。
-
掌握代理模式的应用场景和实现原理
-
了解静态代理和动态代理的区别
-
了解CGlib和JDK Proxy的根本区别
-
手写实现定义的动态代理
优点
- 职责清晰,增强了目标对象的职责。
- 高扩展性,一定程度上降低了系统的耦合程度。
- 将代理对象与目标对象(被代理对象)分离。
- 可以起到保护目标对象的作用。
缺点
- 由于在客户端和目标对象之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂,增加了系统的复杂度。
实例
- 买火车票不一定在火车站买,也可以去代售点。
- 房产中介,代理卖方。
- 微商,代理卖产品。
- spring aop。
使用场景
-
远程代理。
-
虚拟代理。
-
Copy-on-Write 代理。
-
保护(Protect or Access)代理。
-
Cache代理。
-
防火墙(Firewall)代理。
-
同步化(Synchronization)代理。
-
智能引用(Smart Reference)代理。
-
保护目标对象,增强目标对象。
注意事项
1. 和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
2. 和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
静态代理
-
显式声明被代理对象
-
场景模拟:
- 张三(需求方)想吃肯德基,因为懒不想出门,找李四(代理方)代买
IPerson.java
public interface IOrde {
/**
* 订单:下单,接单
*/
void orde();
}
Zhangsan.java
/**
* @author healk
*/
public class ZhangSan implements IOrde {
public void orde() {
System.out.println("张三要吃肯德基");
}
}
Lisi.java
public class Lisi implements IOrde {
private ZhangSan zhangsan;
//构造方法
public Lisi(ZhangSan zhangsan) {
this.zhangsan = zhangsan;
}
public void orde() {
System.out.println("李四,接收张三的需求");
zhangsan.orde();
System.out.println("完成需求");
}
}
public class Test {
public static void main(String[] args) {
Lisi lisi = new Lisi(new ZhangSan());
lisi.orde();
}
}
动态代理
场景模拟:
- 张三想吃肯德基,因为懒不想出门,所以找跑腿代买(代理)。
- 赵六想吃水果,也想找跑腿帮买。
JDK Proxy
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author healk
* @description 实现InvocationHandler接口
*/
public class JdkPaotui implements InvocationHandler {
private IOrde target;
/**
* 拿到目标对象,并创建代理对象
* @param target
* @return
*/
public IOrde getInstance(IOrde obj){
//通过反射获取目标对象
this.target = obj;
Class<?> clazz = target.getClass();
//java.lang.reflect中的Proxy代理类
//此处的this表示InvocationHandler,所以需要去实现InvocationHandler接口
return (IOrde) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
/**
* 重写InvocationHandler接口中invoke
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
//方法调用:动态代用客户端调取的方法,如:zhangsan.orde();
Object result = method.invoke(this.target,args);
after();
return result;
}
private void after() {
System.out.println("完成订单");
}
private void before() {
System.out.println("跑腿,接收等订单");
}
}
public class Test {
public static void main(String[] args) {
JdkPaotui jdkPaotui = new JdkPaotui();
IOrde zhangsan = jdkPaotui.getInstance(new ZhangSan());
zhangsan.orde();
IOrde zhaoliu = jdkPaotui.getInstance(new ZhaoLiu());
zhaoliu.orde();
}
}
public interface IOrde {
/**
* 订单:下单,接单
*/
void orde();
}
public class ZhangSan implements IOrde {
public void orde() {
System.out.println("张三要吃肯德基");
}
}
public class ZhaoLiu implements IOrde {
public void orde() {
System.out.println("赵六要吃水果");
}
}
- JdkPaotui代理类方法中只针对IOrde这一个接口,若需要其他接口时,则不支持。
- 方法升级
public class JdkProxy implements InvocationHandler {
//将目标对象改为范围更大的Object对象
private Object target;
/**
* 拿到目标对象
* @param target 将目标对象改为范围更大的Object对象
* @return
*/
public IOrde getInstance(Object obj){
this.target = obj;
Class<?> clazz = target.getClass();
return (IOrde) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(this.target,args);
after();
return result;
}
private void after() {
System.out.println("完成订单");
}
private void before() {
System.out.println("跑腿,接收等订单");
}
}
cglib Proxy
- 引入依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2</version>
</dependency>
public CglibProxy implements MethodeInterceptor {
public Object getInstance(Class<?> clazz){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
/**
* 重写MethodeInterceptor接口的intercept方法,增强代理类
*/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object obj = methodProxy.invokeSuper(o,objects);
after();
return obj;
}
private void before(){
System.out.println("跑腿,已接单");
System.out.println("已购买");
}
private void after(){
System.out.println("已完成订单");
}
}
public class Customer {
public void orde(){
System.out.println("客户需求:买水果");
}
}
public class CglibTest {
public static void main(String[] args) {
try {
// System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E://cglib_proxy_classes");
Customer obj = (Customer) new CGlibProxy().getInstance(Customer.class);
System.out.println(obj);
obj.orde();
} catch (Exception e) {
e.printStackTrace();
}
}
}
jdkProxy与cglib
-
-
JDK是采用读取接口的信息
-
CGLib覆盖父类方法(生成被代理类的子类)
-
目的:都是生成一个新的类,去实现增强代码逻辑的功能
-
-
- JDK Proxy 对于用户而言,必须要有一个接口实现,目标类相对来说复杂
- CGLib 可以代理任意一个普通的类,没有任何要求
-
- CGLib 生成代理逻辑更复杂,效率,调用效率更高,生成一个包含了所有的逻辑的FastClass,不再需要反射调用
- JDK Proxy生成代理的逻辑简单,执行效率相对要低,每次都要反射动态调用
-
- CGLib 有个坑,CGLib不能代理final的方法,因为CGLib原理是动态生成被代理类的子类,而final方法不会被覆盖。
-
Spring中当Bean有实现接口是,Spring会使用Jdk动态代理 当Bean没有实现接口时,Spring选择CGLib Spring可以通过配置强制使用CGLib,只需在Spring配置文件中加入代码
<aop:aspectj-autoproxy proxy-target-class="true"/>
总结
- JDK中的动态代理是通过反射类Proxy以及InvocationHandler回调接口实现的,但是,JDK中所要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中具有一定的局限性,而且使用反射的效率也并不是很高。
- 使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。