原型模式
一、原型模式(Prototype Pattern)
介绍:原型模式(Prototype Pattern)是指原型实例指定创建对象的 种类,并且通过拷贝这些原型创建新的对象。 调用者不需要知道任何创建细节,不调用构造函数 属于创建型模式 适用场景
类初始化消耗资源较多。 new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等) 构造函数比较复杂。 循环体中生产大量对象时,可读性下降。
总结 :原型模式就是如果快速构建对象的方法总结, 简单工厂将getter、setter封装到某个方法中 JDK提供的实现Cloneable接口,实现快速复制 scope=“prototype”,scope=”singleton” 浅克隆:实现对象拷贝的类A ,需要实现 Cloneable 接口,并覆写 clone() 方法
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。 深克隆:对于 类B 的引用类型的成员变量 T ,需要实现 Cloneable 并重写 clone() 方法
对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个。 对于引用类型,比如数组或者类对象,因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响。
// 浅拷贝
class A
... //属性
privat T obj;
@Override
protected Object clone() throws CloneNotSupportedException {
//Subject 如果也有引用类型的成员属性
return super.clone();
}
//深拷贝
class B
... //属性
privat T obj;
@Override
protected Object clone() throws CloneNotSupportedException {
//Subject 如果也有引用类型的成员属性
B b = super.clone();
b.obj = (T) obj.clone();
retrun b;
}
优点
原型模式性能比直接new一个对象性能高 简化了创建过程 缺点
必须配备克隆(或者可拷贝)方法 对克隆复杂对象或对克隆出的对象进行复杂改造时,易带来风险。 深拷贝、浅拷贝要运用得当 克隆破坏单例模式
Cloneable 源码分析
ArrayList 就实现了 Cloneable 接口 /**
* Returns a shallow copy of this <tt>ArrayList</tt> instance. (The
* elements themselves are not copied.)
*
* @return a clone of this <tt>ArrayList</tt> instance
*/
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
代理模式
一、代理模式(Proxy Pattern)
介绍:代理模式(Proxy Pattern)的定义也非常简单,是指为其他对象提供一种代理,以控制对这个对象的访问。 代理对象在客服端和目标对象之间起到中介作用. 代理模式属于结构型设计模式。使用 代理模式主要有两个目的:
代理类型
//静态代理
// Subject主题接口
public interface Subject {
void doTask();
}
// 真是主题类,真正处理请求的类
public class RealSubject implements Subject {
@Override
public void doTask() {
System.out.println("RealSubject doTask ...");
}
}
// 拦截对真实主题对象访问,代理类
public class Proxy implements Subject {
private Subject realSubject;
public Proxy(Subject realSubject) {
this.realSubject = realSubject;
}
@Override
public void doTask() {
System.out.println("记日志等 .... ");
realSubject.doTask();
}
}
// 客户端代理
public class Client {
public static void main(String[] args) {
Subject realSubject = new RealSubject();
Subject proxy = new Proxy(realSubject);
proxy.doTask();
}
}
//动态代理
Subject 、RealSubject 同上
1) 首先创建调用处理器
//TODO 重点
/*
1. 实现InvocationHandler接口 implements InvocationHandler
2.重写invoke方法 public Object invoke(Object proxy, Method method, Object[] args)
3.重写方法中调用method.invoke(subject, args);
*/
public class SubjectHandler<T extends Subject> implements InvocationHandler {
private T subject;
public SubjectHandler(T subject) {
this.subject = subject;
}
// TODO 2
public T getInstance(T target) throws Exception{
this.target = target;
Class<?> clazz = target.getClass();
return (T)Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在代理真实对象前我们可以添加一些自己的操作
System.out.println("记日志等 .... ");
Object invoke = method.invoke(subject, args);
// 在代理真实对象后我们也可以添加一些自己的操作
System.out.println("task ???");
return invoke;
}
}
2) 创建动态代理
public static void main(String[] args) {
// 我们要代理的真实对象
Subject realSubject = new RealSubject();
// 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandler handler = new SubjectHandler<>(realSubject);
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
*/
Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(), handler);
// 调用代理类方法
subject.doTask();
//TODO 2 调用
RealSubject realSubject = new SubjectHandler<RealSubject>().getInstance(new RealSubject());
Method method = obj.getClass().getMethod("doTask",null);
method.invoke(obj);
}
//CGLib代理
1)首先创建调用处理器
/*
1. MethodInterceptor implements MethodInterceptor
2.重写intercept方法 public Object (Object o, Method method, Object[] objects, MethodProxy methodProxy)
3.重写方法中调用methodProxy.invokeSuper(o,objects);
*/
public class SubjectCGLibHandler<T extends Subject> implements MethodInterceptor {
public Object getCGLibProxy(Class<?> clazz) throws Exception{
//相当于Proxy,代理的工具类
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object obj = methodProxy.invokeSuper(o,objects);
after();
return obj;
}
}
2) 创建动态代理
public static void main(String[] args) {
try {
//查看项目中产生的cglib动态类
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E://cglib_proxy_classes");
RealSubject proxy = (RealSubject) new SubjectCGLibHandler().getCGLibProxy(RealSubject.class);
System.out.println(proxy);
proxy.doTask();
} catch (Exception e) {
e.printStackTrace();
}
}
为什么JDK动态代理中要求目标类实现的接口数量不能超过65535个Class文件是一组以8字节为基础单位的二进制流
各个数据项目严格按照顺序紧凑排列在class文件中
中间没有任何分隔符,这使得class文件中存储的内容几乎是全部程序运行的程序
Java虚拟机规范规定,Class文件格式采用类似C语言结构体的伪结构来存储数据,这种结构只有两种数据类型:无符号数和表
接口索引计数器(interfaces_count),占2字节
参考第一句话:class文件是一组8字节为基础的二进制流,interface_count占2字节。也就是16.00000000,00000000 所以,证明
interface_count的数量最多是2^16次方 最大值=65535
这是在JVM的层面上决定了它的数量最多是65535
且在java源码中也可以看到
if (var2.size() > 65535) {
throw new IllegalArgumentException("interface limit exceeded: " var2.size());
直接做了65535的长度的校验,所以,JDK的动态代理要求,目标类实现的接口数量不能超过65535