代理模式

2. 代理模式

2.1 什么是代理模式?

代理模式是常用的Java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。按照代理类的创建时期,代理类可分为两种。

分类:

  1. 静态代理类:由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
  2. 动态代理类:在程序运行时,运用反射机制动态创建而成。
  3. 还有一种动态代理CGLIB:代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行时,动态修改字节码达到修改类的目的。

2.2 代理模式的关键信息

意图:为其他对象提供一种代理以控制对这个对象的访问。

主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

何时使用:想在访问一个类时做一些控制。

如何解决:增加中间层。

关键代码:实现与被代理类组合。

应用实例:

  1. Windows 里面的快捷方式。
  2. 买火车票不一定在火车站买,也可以去代售点。
  3. 一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。
  4. spring aop。

优点: 1、职责清晰。 2、高扩展性。 3、智能化。

**静态代理和动态代理的区别: **

  1. 静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
  2. 静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
  3. 动态代理是实现JDK里的InvocationHandler接口的invoke方法,但注意的是代理的是接口,也就是你的业务类必须要实现接口,通过Proxy里的newProxyInstance得到代理对象。

2.2 静态代理

​ 静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类

2.2.1 案例分析

具体要求:
1)定义一个接口: Iteacher Dao
2)目标对象 Teacherdao实现接口 Iteacher DAO
3)使用静态代理方式,就需要在代理对象 Teacherdaoproxy中也实现 Iteacher DAO
4)调用的时候通过调用代理对象的方法来调用目标对象。
5)特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法

ITeacherDao

public interface ITeacherDao {
    void teach();
}

TeacherProxyDao

public class TeacherProxyDao implements ITeacherDao {
    private ITeacherDao targetTeacherDao;

    public TeacherProxyDao(ITeacherDao targetTeacherDao) {
        this.targetTeacherDao = targetTeacherDao;
    }

    public TeacherProxyDao() {
    }


    public void teach() {
        System.out.println("代理开始。。。可以执行一些操作 eg:验证数据的正确性等");
        targetTeacherDao.teach();
        System.out.println("代理结束。。。");
    }
}

TeacherDao

public class TeacherDao implements ITeacherDao{
    public void teach() {
        System.out.println("老师授课。。。。");
    }
}

测试

@Test
public void staticProxyTest(){
    //获取目标对象
    TeacherDao teacherDao = new TeacherDao();
    //获取代理对象,将目标对象传给代理对象
    TeacherProxyDao teacherProxyDao = new TeacherProxyDao(teacherDao);
    //使用代理对象调用方法
    teacherProxyDao.teach();
}

2.2.2 静态代理模式的优缺点

优点:静态代理模式可以通过代理对象实现对目标对象功能的扩展,把一些繁琐重复的功能提取出来,目标对象中只保留核心的功能

缺点:静态代理类要在编译时期和目标代理类实现同一个接口,这会导致代理类非常多,且一旦借口要添加功能,代理类和目标类都需要被维护。

2.3 动态代理

2.3.1 什么是动态代理?

动态代理也叫做:JDK 代理、接口代理。代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理。代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象

2.3.2 JDK中动态代理所使用的API

  1. 代理类所在包:jav.lang.reflct.Proxy

  2. JDK 实现代理只需要使用 newProxyInstance 方法,但是该方法需要接收三个参数,完整的写法是:

    static Object newProxyInstance(ClasLoader loader, Clas<?>[] interfaces,InvocationHandler h )
    

2.3.3 动态代理案例

以静态代理为例

思路图解:

image-20200831083136896

ITeacherDao

public interface ITeacherDao {
    void teach();
}

TeacherDao

public class TeacherDao implements ITeacherDao {
    public void teach() {
        System.out.println("老师授课。。。。");
    }
}

TeacherProxyFactory

public Object getProxyInstance(){
    return Proxy.newProxyInstance(targetDao.getClass().getClassLoader(), targetDao.getClass().getInterfaces()
            , new InvocationHandler() {
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    //proxy:就是返回的实例
                    System.out.println("代理开始。。。");
                    //使用反射调用方法
                    Object returnValue = method.invoke(targetDao, args);
                    System.out.println("代理提交。。。");
                    return returnValue;
                }
            });
}

targetDao.getClass().getInterfaces() :用JDK代理时必须让目标类实现接口

Client

public class Client {
    public static void main(String[] args) {
        ITeacherDao teacherDao = new TeacherDao();
        TeacherProxyFactory teacherProxyFactory = new TeacherProxyFactory(teacherDao);
        ITeacherDao proxyInstance = (ITeacherDao) teacherProxyFactory.getProxyInstance();
        proxyInstance.teach();
        //proxyInstance -> class com.sun.proxy.$Proxy0 $表示代理对象
        System.out.println("proxyInstance -> "+ proxyInstance.getClass());
    }
}

2.4 cgLib代理

2.4.1 什么是cglib代理

  1. 静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实
    现任何的接口,这个时候可使用目标对象子类来实现代理-这就是 Cglib 代理
  2. Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib代
    理归属到动态代理。
  3. Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 jav 类与实现 jav 接口.它广泛的被许多 AOP 的
    框架使用,例如 Spring AOP,实现方法拦截
  4. 在 AOP 编程中如何选择代理模式:
    1. 目标对象需要实现接口,用 JDK 代理
    2. 目标对象不需要实现接口,用 Cglib 代理
  5. Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类

2.4.2 cgLib代理实现步骤

**案例: **对TeacherDao实现cglib代理

图解思路

image-20200831102648111

  1. 引入如下包

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.ow2.asm/asm -->
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm</artifactId>
            <version>7.0</version>
        </dependency>
    
        <!-- https://mvnrepository.com/artifact/org.ow2.asm/asm-commons -->
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm-commons</artifactId>
            <version>7.1</version>
        </dependency>
    
        <!-- https://mvnrepository.com/artifact/org.ow2.asm/asm-tree -->
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm-tree</artifactId>
            <version>8.0.1</version>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/cglib/cglib -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>
    
    </dependencies>
    
  2. 实现目标类,不需要继承接口

    public class TeacherDao  {
        public void teach() {
            System.out.println("老师授课。。。。");
        }
    }
    
  3. 创建代理工厂

    1. 代理工厂持有目标对象的引用
    2. 实现getProxyInstance方法
    3. 继承MethodInterceptor接口,实现intercept方法
    public class TeacherProxyFactory implements MethodInterceptor {
        private final TeacherDao targetDao;
    
        public TeacherProxyFactory(TeacherDao teacherDao) {
            this.targetDao = teacherDao;
        }
    
        public Object getProxyInstance(){
            //创建一个工具类
            Enhancer enhancer = new Enhancer();
            //指定父类对象
            enhancer.setSuperclass(targetDao.getClass());
            //设置回调函数
            enhancer.setCallback(this);
            //创建目标对象的代理对象,即目标对象的子类对象
            return enhancer.create();
        }
        //反射生成的子类调用方法必定会被这个方法拦截,对目标对象方法的功能进行扩展
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("代理开始....");
            Object returnValue = method.invoke(targetDao, objects);
            System.out.println("代理提交。。。");
            return returnValue;
        }
    }
    
  4. 实现client类

    public class Client {
        public static void main(String[] args) {
            //获取目标对象
            TeacherDao teacherDao = new TeacherDao();
            //获取代理工厂对象
            TeacherProxyFactory proxyFactory = new TeacherProxyFactory(teacherDao);
            //获取代理对象
            TeacherDao proxyInstance = (TeacherDao) proxyFactory.getProxyInstance();
            proxyInstance.teach();
        }
    }
    

2.4.3 cglib代理和普通的动态代理的区别

  1. cglib代理不需要实现接口
  2. cglib代理生成的对象是目标对象的子类而动态代理生成的是接口的实现类
posted @ 2020-11-03 21:03  ${yogurt}  阅读(111)  评论(0编辑  收藏  举报