单例概念(百度): 单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。
Java中的单例模式: 从项目开始到结束, 某一Java类仅产生一个实例对象; java中的实例一般通过new调用构造创建( 工厂模式除外 ), 为了达到单例的目的, 需要将类的构造方法私有化(private), 仅提供public方法获得实例, 并在该方法中完成单例逻辑;
完整的单例模式示例如下:
饿汉式(类加载时,即第一次用到该类时;):
//饿汉式:创建对象实例的时候直接初始化 空间换时间
public class SingletonOne {
//1、创建类中私有构造
private SingletonOne(){ }
//2、创建该类型的私有静态实例
private static SingletonOne instance=new SingletonOne();
//3、创建公有静态方法返回静态实例对象
public static SingletonOne getInstance(){
return instance;
}
}
懒汉式(使用对象时, 实际创建该类,第一次使用较慢, 此处为保证前程安全, 且基于实用主义仅采用双重校验, 其它实现线程安全的方法还有(静态内部类/枚举等))
//懒汉式:类内实例对象创建时并不直接初始化,直到第一次调用get方法时,才完成初始化操作
//时间换空间
public class SingletonTwo {
//1、创建私有构造方法
private SingletonTwo(){}
//2、创建静态的该类实例对象
private static SingletonTwo instance=null;
//3、创建开放的静态方法提供实例对象
public static SingletonTwo getInstance(){
if (instance == null) {
synchronized (SingletonTwo.class) {
if (instance == null) {
instance = new SingletonTwo();
}
}
}
return instance;
}
}
懒汉式和饿汉式差别在于: 实际创建对象的时机不同, 基于目前Web项目中一般时间(性能)要求大于空间要求, 故: JavaWeb中个人推荐使用饿汉式, 其特点: 利用空间换时间, 项目加载完成后运行响应快, 且直接利用类加载的时机屏蔽了线程安全问题, 使得类的定义相对简单;
|→→→→→→→→→→→→时间线→→→→→→→→→→→→→→→→→→→→
| 饿汉式: 类加载 创建实例 工厂方法被调用 返回实例给调用者
| 懒汉式: 类加载 工厂方法被调用 创建实例 返回实例给调用者
|→→→→→→→→→→→→时间线→→→→→→→→→→→→→→→→→→→→
单例模式在应用层面的特点:
1. 单例的成员变量: 多线程操作时, 该成员变量唯一, 故可以作为全局变量的配置 (但是需要注意的是, 多线程修改时的同步问题, 可以使用同步锁方式避免) ;
2. 单例的方法: 多线程操作时, 可以共享方法, 但是个各个线程又有自己单独的方法局部属性变量, 故可以实现单个实例方法提供全局分别使用的目的, 从而不必每个线程自行实例化对象再调用, 即节约时间又节约空间;
1. 类加载器分类:
i. 启动类加载器(Bootstrap ClassLoader): 加载Java核心库, 属于JVM一部分;
ii. 扩展类加载器(Extendsion ClassLoader): 加载java扩展库;
iii. 应用程序类加载器(Application ClassLoader): 加载用户自定义类;
iiii.自定义类加载器: 本人不了解, 先不讲;
2. 因为讲的都是用户自定义类,所以细讲 iii 的应用程序类加载器:
i. 一般情况下自定义类的加载时机:
a. new对象时;
b. 调用类的静态资源(静态成员变量 / 静态方法)时;
c. 子类加载时;
d. 反射操作时;
e. 带主方法的类;
ii. 在使用类前的适当时机, 预先主动进行类加载:
类加载的本质是将类的字节码文件放入内存中, 当我们需要获取某一个类的字节码对象时, 自然就驱动虚拟机去加载该类:
a. Class.forName(类的全限定名);
b. this.getClass().getClassLoader().loadClass();
另外:在类中定义一个无关的静态成员变量, 需要加载类时, 访问一次该变量即可 (个人感觉此方法优点耍滑头的意思0.0......) ;
-----------------------------------------------------前提知识到此结束-------------------------------------------------------------
JavaWeb的三层架构中, 我们需要预先启动服务器, 服务器启动完毕开始接受用户访问. 而用户访问时, 需要尽可能的快速响应.
其次JavaWeb中, Servlet默认以 单例多线程模式执行,即一个Servlet类仅实例化一个对象;
故:
首先 假设: Service和Dao层每次使用时均需要创建对象, 即: 每次客户端请求Servlet访问, Servlet中每个方法各自创建新的Sercice层对象, 该Sercice层对象的每个方法再各自创建新的Dao层对象. 因为每收到一个请求Servlet都会创建新的线程去处理, 故当前有多少个方法被调用, 就会相应创建多少个Service层和Dao层对象, 严重浪费CPU和内存资源, 同时降低服务器响应速度;
然后 我们针对单个Servlet线程进行优化: 使得每个Servlet的线程中仅产生一个Service层和一个Dao层对象:
要实现该目的, 我们只需保证每个线程中Web层和Service层的方法共用一个Service层和Dao层对象即可, 此时仅需要将每个方法的各自实例化Service和Dao层对象提取为成员对象, 即可实现;
然后 我们针对单个Servlet对象进一步优化, 使得单个Servlet方法执行期间仅产生一个Service层和一个Dao层对象:
回顾Java整体机制, 不难找出最简单有效的方法: 即将Service层和Dao层对象的定义变为static, 因为一个类的静态变量仅此一份, 即可实现;
最后 实际项目中不可能只存在一个Servlet, 即使有BaseServlet帮助我们分模块分功能合并Servlet, 但大型项目功能和模块数量也不可小觑, 故需要再进一步优化, 使得所有Servlet的所有线程在访问同一Service层和Dao层对象时, 使用同一个对象:
或许, 此时部分读者心中有疑问, 这样的话, 还能正确地进行层间参数的传递吗?
答案是肯定的, 原因就是Servlet的单例多线程模式, 而多线程调用同一方法时, 局部变量是每个线程一份的 (因为每个线程都有单独的内存空间) . 而层间参数传递完全可由局部变量+方法参数实现;
继续最后的优化之前简单介绍一下设计模式中的 "工厂模式":
先讲个小案例, 说明工厂模式的应用场景:
Java中万物皆对象思想, 相比大家都已经很熟悉, 生活中一盘菜是对象, 使用的手机也是一部对象.
菜我们可以自己做, 因为其简单, 我们知道使用什么原料, 且更改原料也很简单. 但是手机我们却没办法自己制造, 故需要从网上购买, 电商售卖的手机最终由手机厂家来生产.
而且一般我们并不关系手机的具体原材料, 仅仅关心其功能是否完善好用. 所以,手机完全可以使用不同厂家和规格的原料和不同品牌的组件, 甚至: 同一款手机,也可以使用不同的原料和配件来实现完全相同的功能.
另外,生活中另一个问题, 我们并不是每天都自己做饭, 尤其是快节奏的今天, 此时食堂/饭馆/外卖的意义就出现了, 将大众需求进行统一处理, 降低时间和资源成本, 此处的食堂/饭馆/外卖也可以看做一种工厂.
Java中, 我们在调用某些类的方法时, 内部实现太复杂, 调用者初始化起来比较困难, 仅需要功能, 或者说并不关心类的内部具体怎么实现. 故需要类自己去处理, 并进行自我初始化, 并提供一个静态的共有方法getInstance()返回自身的实例对象, 这就是Java中"工厂模式"达到的的目的之一: 高内聚;
另外, 在Java中, 我们Service层并不想去关心Dao层具体由哪个类实现, 以及如何实现, 就如我们并不关心手机内部天线的生产厂家一样, 故此时我们只需要按手机型号选择即可, 具体实现交给厂家; 这里就引出了Java的面向接口编程, 我们实例化Service层和Dao层对象时, 使用该类对象的接口接收, 好比规定手机型号, 具体实例的创建交给工厂类去做(工厂类加载配置文件....不再分讲). 这里就是Java中"工厂模式"达到的目的之二: 低耦合, 此时工厂类就是解耦的中间人;
第三, 在Java中, 我们有许多工作在每个类中都要进行相同流程的处理, 此时我们就需要流程化工作的抽取, 一般我们定义为工具类, 但在需要时也可以作为 "工厂模式" 的附加功能;
我们想要 "使得所有Servlet的所有线程在访问同一Service层和Dao层对象时, 使用同一个对象" , 就需要用到单例模式. 即在Service层和Dao层所有类均采用单例模式, 这样就初步实现了本阶段优化的目的;
但是:
i. 目前多各类存在高度相同的业务代码, 故需要进行抽取;
ii. 考虑公司数据量陡增, 优化各层实现逻辑等,我们需要尽可能地进行层与层间解耦;
因此:
仅考虑解耦,则只需要结合"面向接口编程"和"工厂模式"即可,除了在调用层采用接口接收之外, 工厂类定义如下:
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.ResourceBundle;
public class BeanFactory2 {
public static Object getinstance(String className){//工厂方法,返回给定类名的实例化对象
ResourceBundle bundle = ResourceBundle.getBundle("beans");//创建ResourceBundle资源加载对象
String classLongName = bundle.getString(className);
try {
return Class.forName(classLongName).getConstructor().newInstance();//实例化对象
} catch (Exception e) {
return null;//若报错返回null
}
}
}
同时考虑解耦和单例模式: 我们抽取成工具类, 而该工具类专门用来实例化单例并返回, 同时考虑上述的解耦思想, 此时把工具类的处理逻辑作为工厂类的附加功能即可, 具体实现如下:
import java.util.Enumeration;
import java.util.HashMap;
import java.util.ResourceBundle;
public class BeanFactory {
private static HashMap objectMap;//定义Map集合,存放类名和实体类Key-Value关系
//饿汉模式
//类加载时实例化出所有单例对象的集合
static{
ResourceBundle bundle = ResourceBundle.getBundle("beans");//创建ResourceBundle资源加载对象
Enumeration keys = bundle.getKeys();//获取配置文件中的所有Key(类名)值枚举
//创建类Map集合,类名作为键,类实例对象作为值
objectMap = new HashMap<>();//实例化Map集合
//遍历枚举对象, 获取所有key值,根据key值获取对象类的全限定名,并实例化对象存放到Map中
while(keys.hasMoreElements()){
String classNameStr = keys.nextElement();//获取单个key
String classLongName = bundle.getString(classNameStr);//获取对应全限定名
try {
Object object = Class.forName(classLongName).getConstructor().newInstance();//实例化对象
objectMap.put(classNameStr,object);//放入Map集合中
} catch (Exception e) {
objectMap.put(classNameStr,null);//若报错放入null
}
}
}
public static Object getinstance(String className){//工厂方法,返回给定类名的实例化对象
return objectMap.get(className);
}
}
请注意: 此时因为枚举是无序的, 在使用单例模式工厂时,除非将dao层类信息单独配置beans属性文件,在service层类对象之前全部加载完毕(或使用其它方法达到类似目的), 否则不要在service层对象中定义全局dao层对象, 此时应该每个方法分别使用getInstance方法获取单例对象的地址来使用;
总结:
1. 以上单例模式工厂类, 在博主的测试项目中运行正常
2. 为了更进一步的提高项目加载的完整度, 可以利用文中方法在项目加载时, 也就是在ServletContextListener监听器中直接使用反射预先加载单例模式工厂类, 从而优化用户的体验;
以上就是博主对"JavaWeb三层架构中Service和Dao层对象单例化的必要性"的论述, 即实现此处的单例化是个很有优势的选择, 同时附带介绍了层间解耦思想,希望对大家有所帮助.
写博客唯一的目的: 本着对读者负责的态度,要求自己以及其严谨的态度学习研究某一方面知识.
转载请标明出处: 划船一哥 :https://blog.csdn.net/weixin_42711325/article/details/83340431