隐身之术:深入解析代理模式的神秘力量
一、定义
代理模式(Proxy Pattern)为其他对象提供一种代理以控制对这个对象的访问,属于结构型模式。
二、解决什么问题
主要解决在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
三、何时使用
想在访问一个类时做一些控制。
四、如何使用
增加中间层
核心思想:
1.提供一个抽象的行为(抽象类或者接口)
2.代理类和被代理对象都实现抽象的行为
3.代理类拥有被代理对象的引用
4.访问代理类
核心角色:
- 抽象行为类
- 代理类
- 被代理对象(具体行为的实现类)
五、静态代理
基础版实现
Image接口:
/**
* <p>
* 图片接口
* </p>
*
* @author shiqi
* @version 1.0.0
* @createTime 2023-12-29
*/
public interface Image {
/**
* 显示方法
*/
void display();
}
具体的RealImage对象
/**
* <p>
* 具体实现类
* </p>
*
* @author shiqi
* @version 1.0.0
* @createTime 2023-12-29
*/
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName){
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName){
System.out.println("Loading " + fileName);
}
}
代理对象ProxyImage
/**
* <p>
* 代理类,代理具体的RealImage对象,在访问RealImage对象时,通过代理类处理
* </p>
*
* @author shiqi
* @version 1.0.0
* @createTime 2023-12-29
*/
public class ProxyImage implements Image {
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
if(realImage == null){
realImage = new RealImage(fileName);
}
}
}
进阶版实现
在执行目标方法前后执行其他的业务逻辑
代理主题角色类ISubject
/**
* <p>
* 代理主题角色类
* </p>
*
* @author shiqi
* @version 1.0.0
* @createTime 2023-12-29
*/
public interface ISubject {
void request();
}
真实主题角色类RealSubject
/**
* <p>
* 真实主题角色类
* </p>
*
* @author shiqi
* @version 1.0.0
* @createTime 2023-12-29
*/
public class RealSubject implements ISubject{
@Override
public void request() {
System.out.println("real service is called.");
}
}
代理主题角色类ProxySubject
/**
* <p>
* 代理主题角色类
* </p>
*
* @author shiqi
* @version 1.0.0
* @createTime 2023-12-29
*/
public class ProxySubject implements ISubject {
private ISubject subject;
public ProxySubject(ISubject subject) {
this.subject = subject;
}
@Override
public void request() {
before();
subject.request();
after();
}
private void before() {
System.out.println("called request before.");
}
private void after() {
System.out.println("called request after.");
}
}
测试客户端
/**
* <p>
* 测试客户端
* </p>
*
* @author shiqi
* @version 1.0.0
* @createTime 2023-12-29
*/
public class ProxySubjectDemo {
public static void main(String[] args) {
ISubject subject = new ProxySubject(new RealSubject());
subject.request();
}
}
运行结果:
六、动态代理实现
JDK动态代理
我们用一个媒婆例子来实现
- 定义一个IPerson接口,提供寻找真爱的方法findLove
public interface IPerson {
void findLove();
}
- 定义具体的媒婆类JdkMeipo
/**
* <p>
* 具体的媒婆
* </p>
*
* @author shiqi
* @version 1.0.0
* @createTime 2023-10-27
*/
public class JdkMeipo implements InvocationHandler {
private IPerson target;
public IPerson getInstance(IPerson target){
this.target = target;
Class<?> clazz = target.getClass();
return (IPerson) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(this.target, args);
after();
return null;
}
private void after() {
System.out.println("双方同意,开始交往");
}
private void before() {
System.out.println("我是媒婆,已经收集到你的需求,开始物色");
}
}
- 定义具体的要找对象的Person类
/**
* <p>
*
* </p>
*
* @author shiqi
* @version 1.0.0
* @createTime 2023-10-27
*/
public class Zhangsan implements IPerson{
@Override
public void findLove() {
System.out.println("张三要求:肤白貌美大长腿");
}
}
- 创建客户端进行测试
/**
* <p>
* 测试客户端
* </p>
*
* @author shiqi
* @version 1.0.0
* @createTime 2023-10-27
*/
public class Test {
public static void main(String[] args) {
JdkMeipo jdkMeipo=new JdkMeipo();
Zhangsan zhangsan=(Zhangsan) jdkMeipo.getInstance(new Zhangsan());
zhangsan.findLove();
}
}
运行结果:
Cglib动态代理
- 定义核心的媒婆类CglibMeipo
import java.lang.reflect.Method;
/**
* <p>
* Cglib的媒婆类
* </p>
*
* @author shiqi
* @version 1.0.0
* @createTime 2023-10-27
*/
public class CglibMeipo implements MethodInterceptor {
private IPerson target;
public Object getInstance(Class<?> clazz) throws Exception {
//相当于Proxy,代理的工具类
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
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("OK的话,准备办事");
}
}
- 定义测试客户端 CglibProxyTest
/**
* <p>
* 测试客户端
* </p>
*
* @author shiqi
* @version 1.0.0
* @createTime 2023-12-29
*/
public class CglibProxyTest {
public static void main(String[] args) throws Exception {
CglibMeipo cglibMeipo = new CglibMeipo();
Zhangsan zhangsan = (Zhangsan) cglibMeipo.getInstance(Zhangsan.class);
zhangsan.findLove();
}
}
Cglib动态代理和Jdk动态代理的区别
CglibProxy和JdkProxy都是Java中用于实现代理模式的两种不同的方式。它们之间的主要区别在于代理的实现方式和对代理目标的要求。
基于继承 vs 基于接口:
CglibProxy使用的是基于继承的代理方式。它通过创建目标类的子类来实现代理。
JdkProxy使用的是基于接口的代理方式。它要求目标类必须实现一个接口,然后通过动态生成实现该接口的代理类。
代理类的生成方式:
CglibProxy通过字节码操作,在运行时动态生成目标类的子类,继承目标类,并重写需要代理的方法。
JdkProxy使用Java的动态代理机制,通过java.lang.reflect.Proxy和InvocationHandler接口来生成代理类,该代理类实现了目标接口,并将方法调用委托给InvocationHandler的实现。
性能:
由于CglibProxy是通过生成目标类的子类,因此在性能上可能会比JdkProxy略慢,尤其是对于目标类的方法调用。
JdkProxy相对来说更轻量,因为它是基于接口的代理,不需要生成子类。
应用场景:
CglibProxy适用于那些没有实现接口的类的代理,因为它是基于继承的。
JdkProxy适用于那些实现了接口的类的代理,且它更符合Java的设计哲学,推崇面向接口编程。
七、代理模式在源码中的体现
- ProxyFactoryBean核心方法getObject (),源码如下:
- Spring AOP
Spring 利用动态代理实现AOP 时有两个非常重要的类:JdkDynamicAopProxy 类和C g l i b A o p P r o x y 类 ,来 看 一 下类 图 ,如 下 图 所 示 。
S p r i n g 中的代理选择原则
- 当 Bean有实现接口时 ,Spring就会用Jdk动态代理。
- 当Bean没有实现接又时,Spring 会选择CGLib代理。
- Spring可 以 通 过 配 置 强 制 使 用 CGLib代 理 ,只 需 在Spring的 配 置 文 件 中 加 入 如 下 代 码 : <aop:aspectj-autoproxy proxy-target-class=“true”/>
八、总结
代理模式的优缺点
优点
- 代理模式能将代理对象与真实被调用目标对象分离
- 在一定程度上降低了系统的耦合性,扩展性好
- 可以起到保护目标对象的作用
- 可以增强目标对象的功能 (代理模式核心的点)
缺点
- 代理模式会造成系统设计中类的数量增加。
- 在客户端和目标对象中增加一个代理对象,会导致请求处理速度变慢
- 增加了系统的复杂度
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!