跳转至

代理模式

1. 介绍

  

代理

  • 静态代理
  • 动态代理
    • JDK代理/接口代理
    • 子代理[GCLib代理],可在内存动态的创建对象,而不需要实现接口

2. 静态代理

2.1 概述

  在不修改目标对象的功能前提下,对目标功能扩展。静态代理在使用的时候需要定义接口,被代理对象[目标对象]与代理对象一起实现相同的接口,然后通过调用相同的方法来调用目标对象的方法。

2.2 示例

ITeacherDao
  |     |_____ TeacherDao
  |___________ TeacherDaoProxy

Client

ITeacherDao.class

package com.cmz.proxy.staticproxy;

public interface ITeacherDao {
    void  teacher();
}

TeacherDao.class

package com.cmz.proxy.staticproxy;

public class TeacherDao implements ITeacherDao {
    @Override
    public void teacher() {
        System.out.println("老师在讲课中");
    }
}

TeacherDaoProxy.class

package com.cmz.proxy.staticproxy;

//静态代理
public class TeacherDaoProxy implements ITeacherDao {

    private ITeacherDao target;//目标对象,通过接口来聚合

    // 构造器
    public TeacherDaoProxy(ITeacherDao target) {
        this.target = target;
    }

    @Override
    public void teacher() {
        System.out.println("代理开始,完成某些操作");

        target.teacher();

        System.out.println("代理结束");
    }
}

Client.class

package com.cmz.proxy.staticproxy;

public class Client {
    public static void main(String[] args) {
        // 创建目标对象[被代理对象]
        TeacherDao teacherDao = new TeacherDao();

        //创建代理对象,同时将被代理对象传递给代理对象
        TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);

        //通过代理对象,调用被代理对象的方法
        // 即:执行的是代理对象的方法,代理对象在去调用目标对象的方法
        teacherDaoProxy.teacher();
    }
}

运行结果

代理开始,完成某些操作
老师在讲课中
代理结束

2.3 优缺点

优点: 在不修改目标对象的功能前提下,能通过代理对象目标功能扩展

缺点: 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,一旦接口增加方法,目标对象与代理对象都要维护。

3. 动态代理

3.1 概述

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

JDK中生成代理对象的API

  1. 代理类所在的包: java.lang.reflect.Proxy
  2. JDK实现代理只需要使用newProxyInstance方法,但是改方法需要接受三个参数,完成的写法
static Object newProxyInstance(ClassLoader loader, Class<?>)

3.2 示例

接口 ITeacherDao.class

package com.cmz.proxy.dynamic;

public interface ITeacherDao {
    void teach(); //授课方法
    void sayHello(String name);
}

接口实现 TeacherDao.class

package com.cmz.proxy.dynamic;

public class TeacherDao implements ITeacherDao {
    @Override
    public void teach() {
        System.out.println("老师正在授课中");
    }

    @Override
    public void sayHello(String name) {
        System.out.println(name+" 老师你好!");
    }
}

代理 ProxyFactory.class

package com.cmz.proxy.dynamic;

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

public class ProxyFactory {

    //维护一个目标对象,object
    private Object target;

    //构造器,对target进行初始化
    public ProxyFactory(Object target) {
        this.target = target;
    }

    //给目标对象,生成一个代理对象

    public Object getProxyInstance(){
        // 说明
        /**
         * public static Object newProxyInstance(ClassLoader loader,
         *                                           Class<?>[] interfaces,
         *                                           InvocationHandler h)
         *  1. ClassLoader loader: 指定当前目标对象使用的类加载器,获取加载器的方法  【固定的】
         *  2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
         *  3. InvocationHandler h: 事情处理,执行目前对象的方法时候,会触发事情处理器方法,会把当前执行的目标对象方法作为参数传入。
         *
         */
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("JDK代理方法");
                        //反射机制调用目标对象的方法
                        Object return_invoke = method.invoke(target, args);
                        System.out.println("JDK代理被提交");
                        return return_invoke;
                    }
                });
    }
}

测试 Client

package com.cmz.proxy.dynamic;

/**
 * @author summer
 * @create 2020-03-06 16:19
 */
public class Client {
    public static void main(String[] args) {
        // 创建目标对象
        ITeacherDao target = new TeacherDao();

        // 给目标对象,创建代理对象,可以转成ITeacherDao
        ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance();
        System.out.println("proxyInstance = "+ proxyInstance+"\n类型是 "+proxyInstance.getClass());

        System.out.println("--------------");
        // 通过代理对象调用目标对象的方法
        proxyInstance.teach();

        System.out.println("-----------------");
        proxyInstance.sayHello("夏天");
    }
}

输出

JDK代理方法
JDK代理被提交
proxyInstance = com.cmz.proxy.dynamic.TeacherDao@4b67cf4d
类型是 class com.sun.proxy.$Proxy0
--------------
JDK代理方法
老师正在授课中
JDK代理被提交
-----------------
JDK代理方法
夏天 老师你好!
JDK代理被提交

3.3 优缺点

  1. 动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。
  2. 动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成 。
  3. 简单来说,动态代理就是交给程序去自动生成代理类。

JDK动态代理实现步骤

  1. 创建被代理的类以及实现的接口;
  2. 创建一个实现接口InvocationHandler的类,它必须实现invoke方法;
  3. 调用Proxy的newProxyInstance静态方法,创建一个代理类。
  4. 通过代理对象调用目标方法。

4. CGLib 代理

4.1 概述

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

注意

  • 在内存中动态构建子类,注意代理的类不能为final
  • 目标对象的方法如果是final/static,那么就不会拦截,即不会执行目标对象额外的业务方法

4.2 示例

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.cmz</groupId>
    <artifactId>DesignPatterns</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.ow2.asm/asm -->
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm</artifactId>
            <version>7.3.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/cglib/cglib -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>
    </dependencies>
</project>

TeacherDao.class

package com.cmz.cglib;

public class TeacherDao  {
    public void teach() {
        System.out.println("老师正在授课中,我是cgblib代理。不需要实现接口");
    }
}

ProxyFactory.class

package com.cmz.cglib;

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 ProxyFactory implements MethodInterceptor {
    // 维护一个目标对象
    private Object target;

    //构造器,传入一个呗代理的对象
    public ProxyFactory(Object target) {
        this.target = target;
    }

    // 返回一个代理对象  是一个target对象的代理对象
    public Object getProxyInstance(){
        //1. 创建一个工具类
        Enhancer enhancer = new Enhancer();
        //2. 设置父类
        enhancer.setSuperclass(target.getClass());
        //3. 设置回调函数
        enhancer.setCallback(this);
        //4. 创建子类对象,即代理对象
        return enhancer.create();
    }

    // 调用目标对象的方法
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib代理开始");
        Object returnVal = method.invoke(target, objects);
        System.out.println("cglib代理提交");
        return returnVal;
    }
}

Client.class

package com.cmz.cglib;


public class Client {
    public static void main(String[] args) {
        // 创建目标对象
        TeacherDao target = new TeacherDao();

        // 获取到代理对象,并且将目标对象传递给代理对象
        TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(target).getProxyInstance();

        //执行代理对象的方法,除非intecept方法,从而实现对目标对象的调用
        proxyInstance.teach();

    }
}