设计模式:06-享元模式 / 代理模式(静态代理·动态代理·cglib代理)

本篇博客主要是学习 韩顺平_Java设计模式 做一个学习笔记使用

12. 享元模式(蝇量模式,池化模式)

12.1 需求的引入

小型的外包项目,给客户 A 做一个产品展示网站,客户 A 的朋友感觉效果不错,也希望做这样的产品展示网 站,但是要求都有些不同: 1) 有客户要求以新闻的形式发布 2) 有客户人要求以博客的形式发布 3) 有客户希望以微信公众号的形式发布

解决方案:

  1. 需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站 的实例对象很多,造成服务器的资源浪费
  2. 解决思路:整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源 都可以达成共享,减少服务器资源
  3. 对于代码来说,由于是一份实例,维护和扩展都更加容易

12.2 基本介绍

  1. 享元模式(Flyweight Pattern) 也叫 蝇量模式: 运用共享技术有效地支持大量细粒度的对象
  2. 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象 中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
  3. 享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对 象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率
  4. 享元模式经典的应用场景就是池技术了,String 常量池、数据库连接池、缓冲池等等都是享元模式的应用,享 元模式是池技术的重要实现方式
  • 享元模式的原理类图
    在这里插入图片描述

  • 对原理图的说明-即(模式的角色及职责)

  1. FlyWeight 是抽象的享元角色, 他是产品的抽象类, 同时定义出对象的外部状态和内部状态的接口或实现
  2. ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务
  3. UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂。
  4. FlyWeightFactory 享元工厂类,用于构建一个池容器(集合), 同时提供从池中获取对象方法
  • 内部状态和外部状态
    比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点,所以棋子颜 色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化 的,所以棋子坐标就是棋子的外部状态
  1. 享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两 个部分:内部状态和外部状态
  2. 内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
  3. 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
  4. 举个例子:围棋理论上有 361 个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有 限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只 有两个实例,这样就很好的解决了对象的开销问题

12.3 应用实例


import java.util.HashMap;
import java.util.Map;

/**
 *
 * 享元模式 (池化技术)
 *
 * @author houyu
 * @createTime 2020/1/1 12:13
 */
public abstract class Demo1 {

    public static void main(String[] args) {

        WebSiteFactory factory = new WebSiteFactory();
        WebSite site1 = factory.get("新闻");
        site1.use();
        WebSite site2 = factory.get("博客");
        site2.use();
        WebSite site3 = factory.get("微信公众号");
        site3.use();
    }
}

/**
 * 抽象的享元角色
 */
abstract class WebSite {
    public abstract void use();
}

/**
 * 具体的享元角色
 */
class ConcreteWebSite extends WebSite {

    private String type;

    public ConcreteWebSite(String type) {
        this.type = type;
    }

    @Override
    public void use() {
        System.out.println("网站的发布形式为:" + type + " 在使用中...");
    }
}

/**
 *  享元工厂类
 */
class WebSiteFactory {

    private Map<String, WebSite> pool = new HashMap<>(8);

    public WebSite get(String type) {
        if(!pool.containsKey(type)) {
            pool.put(type, new ConcreteWebSite(type));
        }
        return pool.get(type);
    }
}

12.4 享元模式在 JDK-Interger 的应用源码分析

  • Integer.valueOf()
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
  • Integer.IntegerCache
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}
  • 缓存 -128 至 127的 Integer 对象

12.5 享元模式的注意事项和细节

  1. 在享元模式这样理解,“享”就表示共享,“元”表示对象
  2. 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元 模式
  3. 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable 存储
  4. 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
  5. 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部 状态的改变而改变,这是我们使用享元模式需要注意的地方.
  6. 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
  7. 享元模式经典的应用场景是需要缓冲池的场景,比如 String 常量池、数据库连接池

13. 代理模式(Proxy)

13.1 代理模式的基本介绍

  1. 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处 是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
  2. 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
  3. 代理模式有不同的形式, 主要有三种 静态代理、动态代理 (JDK 代理、接口代理)和 Cglib 代理 (可以在内存 动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。
  4. 代理模式示意图

在这里插入图片描述

13.2 静态代理

13.2.1 静态代码模式的基本介绍

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

13.2.2 应用实例

/**
 *
 * 静态代理
 *
 * @author houyu
 * @createTime 2020/1/1 13:18
 */
public class StaticProxyDemo {

    public static void main(String[] args) {

        // 1. 创建目标对象(被代理对象)
        Teacher teacher = new Teacher();
        // 2. 创建代理对象,同时将被代理对象传递给代理对象
        TeacherProxy teacherProxy = new TeacherProxy(teacher);
        // 3. 通过代理对象,调用到被代理对象的方法
        //      即:执行的是代理对象的方法,代理对象再去调用目标对象的方法
        teacherProxy.teach();

        /*
         * 前置执行
         * 老师正在上课...
         * 后置执行
         */
    }

}

/**
 * 抽象接口
 */
interface ITeacher {
    /**
     * 抽象方法
     */
    void teach();
}

/**
 * 具体实现层
 */
class Teacher implements ITeacher {

    @Override
    public void teach() {
        System.out.println("老师正在上课...");
    }
}

/**
 * 代理
 */
class TeacherProxy implements ITeacher {

    private ITeacher teacher;

    public TeacherProxy(ITeacher teacher) {
        this.teacher = teacher;
    }

    @Override
    public void teach() {
        System.out.println("前置执行");
        teacher.teach();
        System.out.println("后置执行");
    }
}

13.2.3 静态代理优缺点

优点:

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

缺点:

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

13.3 动态代理

13.3.1 动态代码模式的基本介绍

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

13.3.2 应用实例

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

/**
 *
 * 动态代理
 *
 * @author houyu
 * @createTime 2020/1/1 13:18
 */
public class DynamicProxyDemo {

    public static void main(String[] args) {
        // 1. 创建目标对象(被代理对象)
        Teacher teacher = new Teacher();
        // 2. 创建代理对象,同时将被代理对象传递给代理对象
        ProxyFactory factory = new ProxyFactory(teacher, new PreHandler() {
            @Override
            public void invoke(Object proxy, Method method, Object[] args) {
                System.out.println("前置通知");
            }
        }, new PostHandler() {
            @Override
            public void invoke(Object proxy, Method method, Object result, Object[] args) {
                System.out.println("后置通知");
            }
        });
        // 返回的是一个接口实现层, 并非目标对象哦~~~
        ITeacher proxyInstance = (ITeacher) factory.newProxyInstance();
        proxyInstance.teach();
        /*
         * 前置通知
         * 老师正在上课...
         * 后置通知
         */
    }

    /**
     * 抽象接口
     */
    static interface ITeacher {

        /**
         * 抽象方法
         */
        void teach();
    }

    /**
     * 具体实现层
     */
    static class Teacher implements ITeacher {

        @Override
        public void teach() {
            System.out.println("老师正在上课...");
        }
    }

    /**
     * 代理工厂
     */
    static class ProxyFactory {

        private Object target;
        private PreHandler preHandler;
        private PostHandler postHandler;

        public ProxyFactory(Object target, PreHandler preHandler, PostHandler postHandler) {
            this.target = target;
            this.preHandler = preHandler;
            this.postHandler = postHandler;
        }

        public Object newProxyInstance() {
            // ClassLoader loader,Class<?>[] interfaces,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 {
                    preHandler.invoke(proxy, method, args);
                    Object result = null;
                    try {
                        // 执行实际的方法, 一定是使用目标对象执行(被代理对象), 也就是说代理对象调用被代理对象的方法
                        result = method.invoke(target, args);
                        return result;
                    } finally {
                        postHandler.invoke(proxy, method, result, args);
                    }
                }
            });
        }
    }

    /**
     * 前置通知
     */
    static interface PreHandler {
        void invoke(Object proxy, Method method, Object[] args);
    }

    /**
     * 后置通知
     */
    static interface PostHandler {
        void invoke(Object proxy, Method method, Object result, Object[] args);
    }

}

13.3.3 动态代理优缺点

优点:

  1. 动态创建代理实例

缺点:

  1. 需要目标类需要实现接口(interface)

13.4 Cglib代理(子类代理)

13.4.1 Cglib代理模式的基本介绍

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

13.4.2 应用实例

import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 *
 * Cglib代理
 * compile 'cglib:cglib:3.1'
 *
 * @author houyu
 * @createTime 2020/1/1 13:18
 */
public class CglibProxyDemo {

    public static void main(String[] args) {
        // 1. 创建目标对象(被代理对象)
        Teacher teacher = new Teacher();
        // 2. 创建代理对象,同时将被代理对象传递给代理对象
        ProxyFactory factory = new ProxyFactory(teacher, new PreHandler() {
            @Override
            public void invoke(Object proxy, Method method, Object[] args) {
                System.out.println("前置通知(cglib)");
            }
        }, new PostHandler() {
            @Override
            public void invoke(Object proxy, Method method, Object result, Object[] args) {
                System.out.println("后置通知(cglib)");
            }
        });
        // 返回的是一个接口实现层, 并非目标对象哦~~~
        Teacher proxyInstance = (Teacher) factory.newProxyInstance();
        proxyInstance.teach();
        /*
         * 前置通知
         * 老师正在上课...
         * 后置通知
         */
    }

    /**
     * 具体实现层
     */
    static class Teacher {

        public void teach() {
            System.out.println("老师正在上课...");
        }
    }

    /**
     * 代理工厂
     */
    static class ProxyFactory implements MethodInterceptor {

        private Object target;
        private PreHandler preHandler;
        private PostHandler postHandler;

        public ProxyFactory(Object target, PreHandler preHandler, PostHandler postHandler) {
            this.target = target;
            this.preHandler = preHandler;
            this.postHandler = postHandler;
        }

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

        /**
         * @param object 被代理的对象
         * @param method 代理的方法
         * @param args 方法的参数
         * @param methodProxy CGLIB方法代理对象
         * @return  cglib生成用来代替Method对象的一个对象,使用MethodProxy比调用JDK自身的Method直接执行方法效率会有提升
         * @throws Throwable
         */
        @Override
        public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            preHandler.invoke(object, method, args);
            Object result = null;
            try {
                result = methodProxy.invokeSuper(object, args);
                /* Object result = method.invoke(target, args); */
                return result;
            } finally {
                postHandler.invoke(object, method, result, args);
            }
        }
    }

    /**
     * 前置通知
     */
    static interface PreHandler {
        void invoke(Object proxy, Method method, Object[] args);
    }

    /**
     * 后置通知
     */
    static interface PostHandler {
        void invoke(Object proxy, Method method, Object result, Object[] args);
    }

}

13.4.3 Cglib代理优缺点

优点:

  1. 高效 ( Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类 )
  2. 不需要实现接口, 可以直接对普通类进行代理

缺点:

  1. 在内存中动态构建子类,注意代理的类不能为final,否则报错 java.lang.IllegalArgumentException:
  2. 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法

13.4 几种常见的代理模式介绍—几种变体

  1. 防火墙代理
    内网通过代理穿透防火墙,实现对公网的访问。
  2. 缓存代理
    比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则ok,如果取不到资源,再到公网或者数据库取,然后缓存。
  3. 远程代理
    远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息。
  4. 同步代理
    主要使用在多线程编程中,完成多线程间同步工作
发布了46 篇原创文章 · 获赞 13 · 访问量 2万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 精致技术 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览