本篇博客主要是学习 韩顺平_Java设计模式 做一个学习笔记使用
12. 享元模式(蝇量模式,池化模式)
12.1 需求的引入
小型的外包项目,给客户 A 做一个产品展示网站,客户 A 的朋友感觉效果不错,也希望做这样的产品展示网 站,但是要求都有些不同: 1) 有客户要求以新闻的形式发布 2) 有客户人要求以博客的形式发布 3) 有客户希望以微信公众号的形式发布
解决方案:
- 需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站 的实例对象很多,造成服务器的资源浪费
- 解决思路:整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源 都可以达成共享,减少服务器资源
- 对于代码来说,由于是一份实例,维护和扩展都更加容易
12.2 基本介绍
- 享元模式(Flyweight Pattern) 也叫 蝇量模式: 运用共享技术有效地支持大量细粒度的对象
- 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象 中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
- 享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对 象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率
- 享元模式经典的应用场景就是池技术了,String 常量池、数据库连接池、缓冲池等等都是享元模式的应用,享 元模式是池技术的重要实现方式
-
享元模式的原理类图
-
对原理图的说明-即(模式的角色及职责)
- FlyWeight 是抽象的享元角色, 他是产品的抽象类, 同时定义出对象的外部状态和内部状态的接口或实现
- ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务
- UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂。
- FlyWeightFactory 享元工厂类,用于构建一个池容器(集合), 同时提供从池中获取对象方法
- 内部状态和外部状态
比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点,所以棋子颜 色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化 的,所以棋子坐标就是棋子的外部状态
- 享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两 个部分:内部状态和外部状态
- 内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
- 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
- 举个例子:围棋理论上有 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 享元模式的注意事项和细节
- 在享元模式这样理解,“享”就表示共享,“元”表示对象
- 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元 模式
- 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable 存储
- 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
- 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部 状态的改变而改变,这是我们使用享元模式需要注意的地方.
- 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
- 享元模式经典的应用场景是需要缓冲池的场景,比如 String 常量池、数据库连接池
13. 代理模式(Proxy)
13.1 代理模式的基本介绍
- 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处 是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
- 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
- 代理模式有不同的形式, 主要有三种 静态代理、动态代理 (JDK 代理、接口代理)和 Cglib 代理 (可以在内存 动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。
- 代理模式示意图
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 静态代理优缺点
优点:
- 在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展
缺点:
- 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
- 一旦接口增加方法,目标对象与代理对象都要维护
13.3 动态代理
13.3.1 动态代码模式的基本介绍
- 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
- 动态代理也叫做: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 动态代理优缺点
优点:
- 动态创建代理实例
缺点:
- 需要目标类需要实现接口(interface)
13.4 Cglib代理(子类代理)
13.4.1 Cglib代理模式的基本介绍
- 静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是Cglib代理
2 )Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将Cglib代理归属到动态代理。- Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如SpringAOP,实现方法拦截
- 在AOP编程中如何选择代理模式:1. 目标对象需要实现接口,用JDK代理 2. 目标对象不需要实现接口,用Cglib代理
- 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代理优缺点
优点:
- 高效 ( Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类 )
- 不需要实现接口, 可以直接对普通类进行代理
缺点:
- 在内存中动态构建子类,注意代理的类不能为final,否则报错 java.lang.IllegalArgumentException:
- 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
13.4 几种常见的代理模式介绍—几种变体
- 防火墙代理
内网通过代理穿透防火墙,实现对公网的访问。 - 缓存代理
比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则ok,如果取不到资源,再到公网或者数据库取,然后缓存。 - 远程代理
远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息。 - 同步代理
主要使用在多线程编程中,完成多线程间同步工作