设计模式学习08----之代理模式

概述

今天接着学习设计模式,今天要学习的模式是代理模式。代理模式的应用场景有很多,例如:生活中的代购,明星的经纪人。
定义与结构

代理模式(Proxy)是一种设计模式,为其他对象提供一种代理以控制对这个对象的访问。
在软件开发中有个原则:就是开-闭原则,对新增开放,对修改关闭。尽量不要去修改已经写好的代码。如果需要可以增加一个代理类,来扩展目标代码。
实例

假设某人需要购买一块不在国内销售的化妆品,这时他找到了一个代购来帮他买。
ISubject 买化妆品的接口类,
RealSubject 某人
Proxy 代理类
静态代理模式

静态代理模式,是目标类和代理类都要实现相同的接口。代理角色依赖于真实角色,因为代理角色内部有对真实角色的引用,代理在操作真实角色去执行动作时,必须要知道是哪个真实角色。
接口:

public interface ISubject {
    /**
     *
     * @return
     */
    void buyCosmetics();
}

   

目标类

public class RealSubject  implements ISubject {
    @Override
    public void buyCosmetics() {
        System.out.println("买某种进口化妆品");
    }
}

 

代理类:

public class Proxy implements ISubject {
    //接收目标对象
    private ISubject target;

    public Proxy(ISubject iSubject) {
        this.target = iSubject;
    }

    @Override
    public void buyCosmetics() {
        target.buyCosmetics();
    }
}

    
测试类

public class Client {
    public static void main(String[] args) {
        ISubject realSubject = new RealSubject();
        Proxy proxy = new Proxy(realSubject);
        proxy.buyCosmetics();
    }
}

 

静态代理类的优缺点:
优点:可以在不修改目标类的前提下扩展目标类
缺点:每个目标类都对应一个代理类,当目标类过多时,会导致类急剧增加,而一旦接口增加方法,目标类都代理类都要维护。
针对这个缺点,动态代理可以解决
JDK动态代理

动态代理的特点

    代理对象不需要实现接口
    代理对象的生成是利用JDK的API,动态的在内存中创建代理对象(需要我们制定代理对象/目标对象实现的接口类型)
    动态代理也叫做:JDK代理,接口代理。
    JDK中生成代理对象的API
    代理类所在的包是 :java.lang.reflect.Proxy
    JDK 实现代理只需要调用newProxyInstance方法,该方法需要接受三个参数。

 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h){}

    
该方法是Proxy类中的静态方法, 且接收的三个参数依次为:
ClassLoader loader:指定当前目标对象使用的类加载器,获取类加载器的方法是RealSubject.class.getClassLoader()
Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型。
InvocationHandler h:事件处理,执行目标对象的方法是,会触发时间处理器的方法。会把当前执行目标的方法作为参数传入。
代码示例:
接口类ISubject.java 以及接口实现类,目标对象RealSubject也是一样的,不做修改,在此基础上增加一个代理工厂类(ProxyFactory),将代理类写在这个地方,然后在测试类中先建立目标对象和代理对象的联系,然后代用代理对象中的同名方法。
代理工厂类

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

/**
 * @author xiang.wei
 */
public class ProxyFactory {
    /**
     * 目标对象
     */
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    /**
     * 给目标对象生成代理对象
     * @return
     */
    public Object getProxyInstance() {
        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("前置处理");
                        //执行目标对象方法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("后置处理");
                        return returnValue;
                    }
                }
        );
    }
}


如上,该代理工程实现了InvocationHandler接口,并且实现了接口中的invoke方法,就是在这个方法中加入切面的。
测试类:

public class Client {
    public static void main(String[] args) {
        ISubject target =  new RealSubject();
        ProxyFactory proxyFactory = new ProxyFactory(target);
        ISubject proxy = (ISubject) proxyFactory.getProxyInstance();
        proxy.buyCosmetics();
    }
}

 

总结:
代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能使用动态代理。
Cglib动态代理

以上的静态代理和动态代理模式都要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象。并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式来实现代理,这种方法就叫做:Cglib代理。
Cglib 代理,也叫做子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展,

    JDK 的动态代理有一个限制,就是使用动态代理的对象必须实现一个或者多个接口,如果代理没有实现接口的类,可以使用Cglib实现。

    Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口,他广泛的被许多AOP的框架使用,例如,Spring AOP和synaop,为他们提供方法的interception(拦截)

    Cglib 包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部接口包括class文件的格式和指令集都很熟悉。
    Cglib 子类代理实现方法:

    需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接添加spring-core的依赖即可。

    添加完依赖之后,就可以在内存中动态的构建子类。

    代理类不能为final,否则报错。

    目标对象的方法如果是final/static, 那么就不会被拦截,即不会执行目标对象额外的业务方法。
    代码示例:
    目标对象类:

package com.proxy.cglib;

/**
 * @author xiang.wei
 * @create 2018/5/28 15:02
 */
public class RealSubject {
    public void buyCosmetics() {
        System.out.println("买某种进口化妆品");
    }
}


Cglib 代理类

package com.proxy.cglib;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author xiang.wei
 * @create 2018/5/28 15:03
 */
public class ProxyFactory implements MethodInterceptor{
    //维护目标对象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    /**
     * 给目标对象创建一个代理对象
     * @return
     */
    public Object getProxyInstance() {
        //1.工具类
        Enhancer enhancer = new Enhancer();
        //2.设置代理目标
        enhancer.setSuperclass(target.getClass());
        //3.设置回调函数
        enhancer.setCallback(this);
        //4.创建子类(代理对象)
        return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("前置处理");
        Object returnValue = methodProxy.invoke(target, objects);
        System.out.println("后置处理");
        return returnValue;
    }
}



测试类:

public class Client {
    public static void main(String[] args) {
        //目标对象
        RealSubject realSubject = new RealSubject();
        //代理对象
        RealSubject proxy = (RealSubject) new ProxyFactory(realSubject).getProxyInstance();
        proxy.buyCosmetics();
    }
}

    

总结:
如果代理对象有实现接口,那么使用JDK代理,如果目标对象没有实现接口,那么使用Cglib代理。



作者:码农飞哥
微信公众号:码农飞哥