资料网址:http://c.biancheng.net/view/1338.html
单例模式
资料网站链接:ttps://blog.csdn.net/hzygcs/article/details/88627399
单例模式的定义与特点
单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
在计算机系统中,还有 Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。
单例模式在现实生活中的应用也非常广泛,例如公司 CEO、部门经理等都属于单例模型。J2EE 标准中的 ServletContext 和 ServletContextConfig、Spring 框架应用中的 ApplicationContext、数据库中的连接池等也都是单例模式。
单例模式有 3 个特点:
1、单例类只有一个实例对象;
2、该单例对象必须由单例类自行创建;
3、单例类对外提供一个访问该单例的全局访问点。
单例模式的优点和缺点
单例模式的优点:
单例模式可以保证内存里只有一个实例,减少了内存的开销。
可以避免对资源的多重占用。
单例模式设置全局访问点,可以优化和共享资源的访问。
单例模式的缺点:
单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
单例模式的实现
1、懒汉式单例
public class Singleton {
private static Singleton instance;
//构造方法私有
private Singleton(){}
//提供全局统一实例点
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class){
if (instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
2、饿汉式单例
/**
* 单例模式,饿汉式方式实现
*/
public class Singleton1 {
//类加载时初始化实例对象,只初始化一次
private static Singleton1 instance=new Singleton1();
private Singleton1(){}
/**
* 提供全局统一对象访问点
* 优点: 实现简单,线程安全
* 缺点:1、不管是否使用该对象都会创建,占有内存资源
* 2、如果创建对象过程中需要依赖外部参数无法使用
*/
public static Singleton1 getInstance(){
return instance;
}
}
如何防止反射、克隆、序列化对单例模式的破环
1、防止反射破环(虽然构造方法已私有化,但通过反射机制使用newInstance()方法构造方法也是可以被调用):
- 首先定义一个全局变量开关isFrist默认为开启状态
- 然后在构造方法里双重校验,如果是第一次访问将isFirst标记改为false,否则抛出异常。
private static boolean isFirst=true;
private Singleton(){
if (isFirst){
synchronized (Singleton.class){
if (isFirst){
isFirst=false;
}
}
}else {
throw new RuntimeException("单例已经存在,不能创建其他实例对象");
}
}
2、防止克隆破环
- 实现Cloneable接口,重写clone(),直接返回单例对象
@Override
protected Object clone() throws CloneNotSupportedException {
return instance;
}
3、防止序列化破环
添加readResolve(),返回Object对象
private Object readResolve(){
return instance;
}
测试代码:
//序列化攻击单例
/* ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("d:\\test\\a.txt"));
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("d:\\test\\a.txt"));
Singleton instance = Singleton.getInstance();
System.out.println("序列化"+instance.hashCode());
oos.writeObject(instance);
Object o = ois.readObject();
System.out.println("反序列化"+o.hashCode());*/
/**
* 反射攻击单例
*/
/* Class<Singleton> cls = Singleton.class;
Constructor<Singleton> con = cls.getDeclaredConstructor();
con.setAccessible(true);
Singleton instance1 = con.newInstance();
System.out.println(instance1.hashCode());*/
/**
* 克隆攻击单例
*/
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = (Singleton)instance1.clone();
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
简单工厂模式
优点和缺点
优点:
1、工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。
2、客户端无需知道所创建具体产品的类名,只需知道参数即可。
3、也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。
缺点:
1、简单工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则。
2、使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度
3、系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂
4、简单工厂模式使用了 static 工厂方法,造成工厂角色无法形成基于继承的等级结构。
模式的结构与实现
简单工厂模式的主要角色如下:
- 简单工厂(SimpleFactory):是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被 外界直接调用,创建所需的产品对象。
- 抽象产品(Product):是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。
- 具体产品(ConcreteProduct):是简单工厂模式的创建目标。
/**
* 汽车工厂类
*/
public class CarFactory {
private static CarFactory instance=new CarFactory();
private CarFactory(){}
public static CarFactory getInstance(){
return instance;
}
/**
* 制造汽车,根据类型的到不同类型的汽车
* 缺点:随着产品不断增加,相应的业务逻辑要跟着修改,不满足开闭原则,不属于23种设计模式
* @param type
* @return
*/
public Car makeCar(int type){
switch (type){
case 1:
return new AudiCar();
case 2:
return new BenCar();
default:
break;
}
return null;
}
//通过反射生产对象
public Car makeCar(String carName){
try {
Class<?> c = Class.forName(carName);
Constructor<?> con = c.getConstructor();
return (Car) con.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
工厂方法模式
优点:
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
- 灵活性增强,对于新产品的创建,只需多写一个相应的工厂类。
- 典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。
缺点: - 类的个数容易过多,增加复杂度
- 增加了系统的抽象性和理解难度
- 抽象产品只能生产一种产品,此弊端可使用抽象工厂模式解决。
/**
* 具体的汽车工厂
*/
public class AudiCarFactory implements CarFactory{
private static AudiCarFactory instance=new AudiCarFactory();
public static AudiCarFactory getInstance(){
return instance;
}
@Override
public Car makeCar() {
return new AudiCar();
}
}
抽象工厂模式
模式的定义与特点
抽象工厂(AbstractFactory)模式的定义:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
使用抽象工厂模式一般要满足以下条件。
系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
系统一次只可能消费其中某一族产品,即同族的产品一起使用。
抽象工厂模式除了具有工厂方法模式的优点外,其他主要优点如下。
可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品组。
抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则。
其缺点是:当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。增加了系统的抽象性和理解难度。
/**
* 具体奥迪工厂
*/
public class AudiFactory implements Factory{
private static volatile AudiFactory instance;
private AudiFactory(){}
public static AudiFactory getInstance(){
if (instance==null){
synchronized (AudiFactory.class){
if (instance==null){
instance=new AudiFactory();
}
}
}
return instance;
}
@Override
public Car makeCar() {
return new AudiCar();
}
@Override
public Bicycle makeBicycle() {
return new AudiBicycle();
}
代理模式
静态代理
第一步、创建服务类(被代理类)接口
public interface Landlord {
public void rent();
public void water();
}
第二步、实现服务(被代理类)接口
public class LandlordOne implements Landlord{
@Override
public void rent() {
System.out.println("一号房东出租房子,房租3000,押一付一");
}
@Override
public void water() {
System.out.println("房东抄水表,收水费");
}
}
第三步、创建代理对象
/**
* 代理类,用来代理目标对象(房东),静态代理模式
* 缺点:代理对象跟目标对象实现同一接口,如果目标对象业务方法发生改变,代理类跟着发生改变
* 可以使用动态代理反射实现,动态代理分两种:jdk动态代理(jdk自带,要求代理的目标对象必须要实现接口),cglib动态代理(任意目标对象)
*/
public class Proxy implements Landlord{
private Landlord landlord;
public Proxy(){}
public Proxy(Landlord landlord){
this.landlord=landlord;
}
@Override
public void rent() {
System.out.println("收中介费100元");
landlord.rent();
}
@Override
public void water() {
System.out.println("收手续费10元");
landlord.water();
}
}
第四步、编写测试类
public class Test {
public static void main(String[] args) {
//租客直接找房东租房子
LandlordOne one=new LandlordOne();
one.rent();
//通过代理找房东租房子
Proxy proxy=new Proxy(new LandlordOne());
proxy.rent();
proxy.water();
}
}
静态代理总结:
-
优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
-
缺点:我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
jdk动态代理
在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK再运行时为我们动态的来创建。
动态代理有两种方式:JDK动态代理,cglib动态代理
这里我们说下JDK动态代理,cglib动态代理等学习spring框架在讲解
第一步、编写动态处理器
**
* JDK动态代理
*/
public class JDKProxy implements InvocationHandler {
public Object targetObject;
public Object createProxyInstance(Object targetObject){
this.targetObject=targetObject;
return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),
this.targetObject.getClass().getInterfaces(),
this);
}
/**
*
* @param proxy 代理对象
* @param method 目标对象的方法
* @param args 目标对象的方法参数
* @return 目标对象的方法返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("安全校验");
Object obj = method.invoke(this.targetObject, args);
System.out.println("日志记录");
return obj;
}
第二步、编写测试类
public static void main(String[] args) {
//目标对象
UserDao user=new UserDaoImpl();
JDKProxy jdkProxy = new JDKProxy();
//创建代理对象
UserDao proxy = (UserDao)jdkProxy.createProxyInstance(user);
proxy.delete();
}
注意Proxy.newProxyInstance()方法接受三个参数:
- ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
- Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
- InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法
JDK动态代理目标对象必须实现接口
动态代理总结:
虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。有很多条理由,人们可以否定对 class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。接口和类的划分,本就不是很明显,只是到了Java中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。但是,不完美并不等于不伟大,伟大是一种本质,Java动态代理就是佐例。
首先分析代理对象的产生过程,最后分析代理对象的产生后的样子
底层原理:
产生过程:
Proxy.newInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this) 方法,该方法主要做了
①获取代理类的接口Subject.class对象的副本intfs。
②调用getProxyClass0(loader, intfs)方法,获取代理类的class对象
–2.1 getProxyClass方法会去调用proxyClassCache.get(loader,interfaces)方法,该方法的作用,如果缓存中已经有该接口对应的代理类的副本,那么返回代理类的副本,否则使用ProxyClassFactory的apply方法创建一个class对象;get方法中有一句表现了这个过程,如下图
SubkeyFactory与ProxyClassFactory都是BiFunction接口的的实现类,此处调用的是ProxyClassFactory的apply方法。
----2.1.1 apply方法:根据接口的全限定类名以及指定的classLoader,调用class.forName方法得到接口的class对象interfaceClass,进行判断是否是接口类型,是否有重复,
----2.1.2 设定代理类所在的包路径,如果有非public接口,且不是在同一个包内,抛异常,如果在同一个包内,就设定代理类的包路径为proxyPkg,如果都是public接口,那么生成的代理类的包路径proxyPkg设定为com.sum.proxy.
----2.1.3 根据包路径,类名前缀和num数字,确定代理类的名字
----2.1.4 调用ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);内部生成一个ProxyGenerator对象,调用ProxyGenerator.generateClassFile()得到代理类的字节码文件;
------2.1.4.1 ProxyGenerator.generateClassFile()方法,为代理类添加了三个object类的hashcode,equals,toString方法,遍历父接口,获取每个接口中的方法,添加方法,添加构造方法等,都加入到代理中;返回二进制字节码文件
----2.1.5 调用defineClass0(loader, proxyName, proxyClassFile, 0,proxyClassFile.length);将字节码文件创建为class对象,返回
③获取到代理类的class对象后,得到代理类参数为InvocationHandler的构造方法,如果是非public类型,就使用setAccessible设定为true,然后调用cons.newInstance(new Object[]{h})来生成代理类对象;
④在代理类对象中,有个request方法,方法中默认会调用Proxy类里面的InvocationHandler也就是我们自定义的InvocationHandler的invoke方法。这就实现了动态代理
Cglib动态代理对象
-
CGLIB(Code Generation Library),Code生成类库
-
CGLIB动态代理不限定是否具有接口,可以对任意操作进行增强
-
CGLIB动态代理无需要原始被代理对象,动态创建出新的代理对象
原理图:
两个代码例子:
//例子一
public class UserServiceImplCglibProxy {
public static UserServiceImpl createUserServiceCglibProxy(Class clazz){
//创建Enhancer对象(可以理解为内存中动态创建了一个类的字节码)
Enhancer enhancer = new Enhancer();
//设置Enhancer对象的父类是指定类型UserServerImpl
enhancer.setSuperclass(clazz);
Callback cb = new MethodInterceptor() {
public Object intercept(Object o, Method m, Object[] a, MethodProxy mp) throws Throwable {
Object ret = mp.invokeSuper(o, a);
if(m.getName().equals("save")) {
System.out.println("刮大白");
}
return ret;
}
};
//设置回调方法
enhancer.setCallback(cb);
//使用Enhancer对象创建对应的对象
return (UserServiceImpl)enhancer.create();
}
}
//例子二
public class CglibProxy implements MethodInterceptor {
private Object targetObject;
public Object createProxyInstance(Object targetObject){
this.targetObject=targetObject;
//该类用于生成代理对象
Enhancer enhancer=new Enhancer();
//设置父类
enhancer.setSuperclass(this.targetObject.getClass());
//设置回调对象为本身
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if (method.getName().contains("delete")||method.getName().contains("update")){
System.out.println("安全校验");
}
Object invoke = method.invoke(this.targetObject, objects);
System.out.println("日志记录");
return invoke;
}
}
观察者模式
观察者模式可以看做是一个通知模式,就是说有谁想了解你或者想从你这里的到消息,可以通过提前关注,然后等待被关注着法送信息或者动作。
实现步骤如下:
第一步、创建观察者的接口
/**
* 抽象观察者对象
*/
public interface Observer {
public void response(String msg);
}
第二步、实现观察者,并重写抽象方法
public class ObserverOne implements Observer{
@Override
public void response(String msg) {
System.out.println("ObserverOne粉丝收到:"+msg );
}
}
第三步、创建目标对象的抽象类
/**
* 抽象目标对象
*/
public abstract class Subject {
protected List<Observer> list=new ArrayList<>();
public synchronized void add(Observer observer){
list.add(observer);
}
public synchronized void remove(Observer observer){
list.remove(observer);
}
public abstract void notifyObserver();
}
第四步、实现通过继承实现抽象方法,通过遍历调用所有关注的观察者对象并调用相关方法
public class SubjectOne extends Subject{
@Override
public void notifyObserver() {
list.stream().forEach(observer -> observer.response("明星SubjectOne结婚啦"));
}
}
第五步、编写测试类
public static void main(String[] args) {
//创建目标对象
SubjectOne subjectOne=new SubjectOne();
//创建观察者对象
ObserverOne observerOne = new ObserverOne();
ObserverTow observerTow = new ObserverTow();
//给目标对象添加观察者对象
subjectOne.add(observerOne);
subjectOne.add(observerTow);
//目标对象发布信息通知所有观察者
subjectOne.notifyObserver();
//删除观察者对象
subjectOne.remove(observerOne);
//目标对象发布信息通知所有观察者
subjectOne.notifyObserver();