Java设计模式(八)----代理模式

简介:

代理描述
1.生活中:
代理就是一个人或者一个组织代表其他人去做一件事的现实生活中的。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
2.官方:
代理模式是对象的结构模式。代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用

一、静态代理
类图结构如下
这里写图片描述


在代理模式中的角色:

  ●  抽象主题角色:声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。

  ●  真实主题角色:定义了代理对象所代表的目标对象。

  ●  代理主题角色:代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象。它可以增加一些真实主题里面没有的功能。


生活中的例子:过年加班比较忙,没空去买火车票,这时可以打个电话到附近的票务中心,叫他们帮你买张回家的火车票,当然这会附加额外的劳务费。但要清楚票务中心自己并不卖票,只有火车站才真正卖票,票务中心卖给你的票其实是通过火车站实现的。这点很重要!

上面这个例子,你就是“客户”,票务中心就是“代理角色”,火车站是“真实角色”,卖票称为“抽象角色”!

代码

抽象主题角色

//抽象角色:声明真实对象和代理对象的共同接口;
public interface  TicketManager { 
    /**
     * 售票
     */
    public  void  soldTicket();
    /**
     * 改签
     */
    public void changeTicket();
    /**
     * 退票
     */
    public void returnTicket();
}   

真实主题角色

public class TicketManagerImpl implements TicketManager {

    @Override
    public void soldTicket() {
        System.out.println("售票");
    }

    @Override
    public void changeTicket(){

        System.out.println("改签");
    }

    @Override
    public void returnTicket() {
        System.out.println("退票");
    }

}

代理主题角色(添加了身份验证功能)

public class StaticProxyTicketManager implements TicketManager {
    TicketManager ticketManager;//目标对象的引用

    public StaticProxyTicketManager(TicketManager ticketManager) {
        this.ticketManager = ticketManager;
    }

    @Override
    public void soldTicket() {
        checkIdentity();//前置增强
        ticketManager.soldTicket();
    }

    @Override
    public void changeTicket() {
        checkIdentity();
        ticketManager.changeTicket();
    }

    @Override
    public void returnTicket() {
        checkIdentity();
        ticketManager.changeTicket();
    }
    /**
     * 身份验证
     */
    public void checkIdentity(){
        System.out.println("身份验证--------------");
    }

}

第二个代理主题角色(添加了日志功能)

//代理类  实现同一个接口
public class LogProxy implements TicketManager {
    TicketManager ticketManager;//目标类的引用
    public LogProxy(TicketManager ticketManager){
        this.ticketManager=ticketManager;
    }
    @Override
    public void soldTicket() {
        ticketManager.soldTicket();
        log();//后置增强
    }

    @Override
    public void changeTicket() {
        ticketManager.changeTicket();
        log();
    }

    @Override
    public void returnTicket() {
        ticketManager.returnTicket();
        log();

    }
    //增强
    private void log() {
        System.out.println("日志...");

    }

}

客户端

public class Test {
    public static void main(String[] args) {
        //装饰模式   new TicketManagerImpl()  真实的目标对象
        //TicketManager tm=new StaticProxyTicketManager(new TicketManagerImpl());
        TicketManager tm=new LogProxy(new StaticProxyTicketManager(new TicketManagerImpl()));

        tm.soldTicket();
        tm.changeTicket();
        tm.returnTicket();
    }
}

结果:
身份验证————–
售票
日志…
身份验证————–
改签
日志…
身份验证————–
改签
日志…

从上面例子可以看出 客户端通过代理来购票 而代理实际上不能卖票给客户,他实际上是通过目标对象卖票给客户的,也就是说他是通过真实主题的目标对象实现给客户端卖票的功能,他只是一个中介,但我们可以在它里面增加一些功能,比如身份验证或者宣传打广告等其他的功能。


静态代理类:在程序运行前,代理类的.class文件就已经存在了,已确定被代理的对象
静态代理:
优点:对真实对象进行封装,不会修改目标类的代码。
缺点:
1.多个不同类型目标对象需要代理时,我就需要建立多个代理类,造成类的膨胀
2.代码的冗余
3.编译期加入,不够灵活


二、动态代理
描述(这个描述从网上看到的,相对比较容易理解)
动态代理(Dynamic Proxy):相比静态代理,动态代理具有更强的灵活性,因为它不用在我们设计实现的时候就指定某一个代理类来代理哪一个被代理对象,我们可以把这种指定延迟到程序运行时由JVM来实现。

所谓代理,就是需要代理类和被代理类有相同的对外接口或者说成服务,所以代理类一般都必须实现了所有被代理类已实现的接口,因为接口就是制定了一系列对外服务的标准。

1.JDK实现动态代理


正因为动态代理有这样灵活的特性,所以我们在设计动态代理类(DynamicProxy)时不用显式地让它实现与真实主题类(RealSubject)相同的接口(interface),而是把这种实现推迟到运行时。

为了能让DynamicProxy类能够在运行时才去实现RealSubject类已实现的一系列接口并执行接口中相关的方法操作,需要让DynamicProxy类实现JDK自带的java.lang.reflect.InvocationHandler接口,该接口中的invoke()方法能够让DynamicProxy实例在运行时调用被代理类的“对外服务”,即调用被代理类需要对外实现的所有接口中的方法,也就是完成对真实方法的调用,Java帮助文档中称这些真实方法为处理程序。

按照上面所述,我们肯定必须先把被代理类RealSubject已实现的所有interface都加载到JVM中,不然JVM怎么能够找到这些方法呢?明白了这个道理,那么我们就可以创建一个被代理类的实例,获得该实例的类加载器ClassLoader。

所谓的类加载器ClassLoader,就是具有某个类的类定义,即类的内部相关结构(包括继承树、方法区等等)。

更重要的是,动态代理模式可以使得我们在不改变原来已有的代码结构的情况下,对原来的“真实方法”进行扩展、增强其功能,并且可以达到控制被代理对象的行为的目的。请详看下面代码中的DynamicProxy类,其中必须实现的invoke()方法在调用被代理类的真实方法的前后都可进行一定的特殊操作。这是动态代理最明显的优点


类图
这里写图片描述

代码

package com.yc.spring.staticproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxyTicketManager implements InvocationHandler {
    private Object targetObject;

    /**
     * 目标的初始化方法,根据目标生成代理类
     * 
     * @param targetObject
     * @return
     */
    public Object newProxyInstance(Object targetObject) {
        this.targetObject = targetObject;
        // 第一个参数,目标对象 的装载器
        // 第二个参数,目标接口已实现的所有接口,而这些是动态代理类要实现的接口列表
        // 第三个参数, 调用实现了InvocationHandler的对象生成动态代理实例,当你一调用代理,代理就会调用InvocationHandler的invoke方法
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(),
                this);
    }

    /**
     * 反射,这样你可以在不知道具体的类的情况下,根据配置的参数去调用一个类的方法。在灵活编程的时候非常有用。
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 检查
        checkIdentity();
        Object ret = null;
        try {
            // 调用目标方法
            ret = method.invoke(targetObject, args);
            // 执行成功,打印成功信息
            log();
        } catch (Exception e) {
            e.printStackTrace();
            // 失败时,打印失败信息
            System.out.println("error-->>" + method.getName());
            throw e;
        }
        return ret;
    }

    /**
     * 身份验证
     */
    public void checkIdentity(){
        System.out.println("身份验证--------------");
    }
    public void log(){
        System.out.println("日志..." );
    }

}

客户端

public class Test {
    public static void main(String[] args) {
        DynamicProxyTicketManager dynamicProxyTicketManager=new DynamicProxyTicketManager();
        TicketManager tm=(TicketManager) dynamicProxyTicketManager.newProxyInstance(new TicketManagerImpl());

        tm.soldTicket();
        tm.changeTicket();
        tm.returnTicket();
    }
}

结果同上

优缺点

优点:

1、一个动态代理类更加简单了,可以解决创建多个静态代理的麻烦,避免不断的重复多余的代码

2、调用目标代码时,会在方法“运行时”动态的加入,决定你是什么类型,才调谁,灵活

缺点:

1、系统灵活了,但是相比而言,效率降低了,比静态代理慢一点

2、动态代理比静态代理在代码的可读性上差了一点,不太容易理解

3、JDK动态代理只能对实现了接口的类进行代理

总结
各有各的好,具体情况具体讨论


2.Cglib实现动态代理


描述(网上整理)
AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。
两种方法同时存在,各有优劣。jdk动态代理是由java内部的反射机制来实现的 ,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛, 且在效率上更有优势。

JDK的动态代理机制只能代理实现了接口的类,否则不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。


介绍:
CGLIB的核心类:
net.sf.cglib.proxy.Enhancer – 主要的增强类
net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是Callback接口的子接口,需要用户实现
net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用,如使用:
Object o = methodProxy.invokeSuper(proxy, args);//虽然第一个参数是被代理对象,也不会出现死循环的问题。
net.sf.cglib.proxy.MethodInterceptor接口是最通用的回调(callback)类型,它经常被基于代理的AOP用来实现拦截(intercept)方法的调用。这个接口只定义了一个方法
public Object intercept(Object object, java.lang.reflect.Method method,
Object[] args, MethodProxy proxy) throws Throwable;

第一个参数是代理对像,第二和第三个参数分别是拦截的方法和方法的参数。原来的方法可能通过使用java.lang.reflect.Method对象的一般反射调用,或者使用 net.sf.cglib.proxy.MethodProxy对象调用。net.sf.cglib.proxy.MethodProxy通常被首选使用,因为它更快


代码

public class CglibDynamicProxyTicketManager implements MethodInterceptor  {
    private Object targetObject;//目标对象
    /** 
     * 创建代理对象 
     *  
     * @param targetObject 
     * @return 
     */  
    public Object getInstance(Object targetObject) {  
        this.targetObject = targetObject;  
        Enhancer enhancer = new Enhancer();  // 用这个类来创建代理对象(被代理类的子类): 并设置父类;设置回调;
        enhancer.setSuperclass(this.targetObject.getClass()); // 设置被代理类作为其父类的代理目标
        // 回调方法  
        enhancer.setCallback(this);  // 设置回调--当这个代理对象的方法被调用时 回调方法intercept()会被执行
        // 创建代理对象  
        return enhancer.create();  
    }  

    @Override
    //回调方法
    // methodProxy 代理的类的方法
    /**
     * methodProxy 会调用父类(目标对象)的被代理的方法,比如soldTicket方法等
     */
    public Object intercept(Object obj, Method method, Object[] args,  
            MethodProxy methodProxy) throws Throwable {
        Object result = null;
        checkIdentity();//前置增强
        result=methodProxy.invokeSuper(obj, args); //调用新生成的cglib的代理对象 所属的父类的被代理的方法
         log();//后置增强
        return result;
    }

    /**
     * 身份验证
     */
    public void checkIdentity(){
        System.out.println("身份验证--------------");
    }
    public void log(){
        System.out.println("日志..." );
    }


}

客户端

public class Test {
    public static void main(String[] args) {
        CglibDynamicProxyTicketManager cglibdynamicProxyTicketManager=new CglibDynamicProxyTicketManager();
        //生成代理对象
        TicketManager tm=(TicketManager) cglibdynamicProxyTicketManager.getInstance(new TicketManagerImpl());

        tm.soldTicket();//当调用代理对象的被代理对象的方法时  会自动回调 代理类中的Intercept()方法
        tm.changeTicket();
        tm.returnTicket();
    }
}

结果同上

目录
相关文章
|
2月前
|
设计模式 Java Spring
Java 设计模式之责任链模式:优雅处理请求的艺术
责任链模式通过构建处理者链,使请求沿链传递直至被处理,实现发送者与接收者的解耦。适用于审批流程、日志处理等多级处理场景,提升系统灵活性与可扩展性。
288 2
|
2月前
|
设计模式 网络协议 数据可视化
Java 设计模式之状态模式:让对象的行为随状态优雅变化
状态模式通过封装对象的状态,使行为随状态变化而改变。以订单为例,将待支付、已支付等状态独立成类,消除冗长条件判断,提升代码可维护性与扩展性,适用于状态多、转换复杂的场景。
328 0
|
4月前
|
设计模式 缓存 Java
Java设计模式(二):观察者模式与装饰器模式
本文深入讲解观察者模式与装饰器模式的核心概念及实现方式,涵盖从基础理论到实战应用的全面内容。观察者模式实现对象间松耦合通信,适用于事件通知机制;装饰器模式通过组合方式动态扩展对象功能,避免子类爆炸。文章通过Java示例展示两者在GUI、IO流、Web中间件等场景的应用,并提供常见陷阱与面试高频问题解析,助你写出灵活、可维护的代码。
|
2月前
|
设计模式 算法 搜索推荐
Java 设计模式之策略模式:灵活切换算法的艺术
策略模式通过封装不同算法并实现灵活切换,将算法与使用解耦。以支付为例,微信、支付宝等支付方式作为独立策略,购物车根据选择调用对应支付逻辑,提升代码可维护性与扩展性,避免冗长条件判断,符合开闭原则。
359 35
|
2月前
|
设计模式 消息中间件 传感器
Java 设计模式之观察者模式:构建松耦合的事件响应系统
观察者模式是Java中常用的行为型设计模式,用于构建松耦合的事件响应系统。当一个对象状态改变时,所有依赖它的观察者将自动收到通知并更新。该模式通过抽象耦合实现发布-订阅机制,广泛应用于GUI事件处理、消息通知、数据监控等场景,具有良好的可扩展性和维护性。
279 8
|
7月前
|
设计模式 缓存 安全
【高薪程序员必看】万字长文拆解Java并发编程!(8):设计模式-享元模式设计指南
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的经典对象复用设计模式-享元模式,废话不多说让我们直接开始。
175 0
|
4月前
|
设计模式 安全 Java
Java设计模式(一):单例模式与工厂模式
本文详解单例模式与工厂模式的核心实现及应用,涵盖饿汉式、懒汉式、双重检查锁、工厂方法、抽象工厂等设计模式,并结合数据库连接池与支付系统实战案例,助你掌握设计模式精髓,提升代码专业性与可维护性。
|
4月前
|
设计模式 XML 安全
Java枚举(Enum)与设计模式应用
Java枚举不仅是类型安全的常量,还具备面向对象能力,可添加属性与方法,实现接口。通过枚举能优雅实现单例、策略、状态等设计模式,具备线程安全、序列化安全等特性,是编写高效、安全代码的利器。
|
7月前
|
设计模式 Java 数据库连接
【设计模式】【结构型模式】代理模式(Proxy)
一、入门 什么是代理模式? 代理模式(Proxy Pattern)是一种结构型设计模式,允许你提供一个代理对象来控制对另一个对象的访问。 代理对象在客户端和目标对象之间起到中介作用,可以在不改变目标对
196 10
|
9月前
|
设计模式 Java 数据安全/隐私保护
Java 设计模式:装饰者模式(Decorator Pattern)
装饰者模式属于结构型设计模式,允许通过动态包装对象的方式为对象添加新功能,提供比继承更灵活的扩展方式。该模式通过组合替代继承,遵循开闭原则(对扩展开放,对修改关闭)。