[学习笔记] SpringMVC之单例模式详解
# 学习 # · 2021-02-18
单例模式
1、一些常用的底层操作,比如数据库连接,获取数据库配置文件的操作等等,我们可以将这些操作提取到一个工具类(ConfigManager.java),并把它设计为单例模式。
2、单例模式:23种设计模式之一,最常用的设计模式。在系统运行期间,有且只有一个实例,满足三个关键点:
(1)一个类只有一个实例。因此,只能提供私有的构造器,即保证不能随便创建该类实例。
public class ConfigManager {
private static Properties properties;
//私有构造器,读取数据库配置文件
//有效保证I/O操作在整个系统运行期间仅被执行一次,已解决资源消耗问题。
private ConfigManager(){
String configFile = "database.properties";
properties = new Properties();
InputStream is = ConfigManager.class.getClassLoader().getSystemResourceAsStream(configFile);
try {
properties.load(is);
is.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
(2)它必须自行创建这个实例。
public class ConfigManager {
//定义了静态私有对象ConfigManager
private static ConfigManager configManager;
private static Properties properties;
//private constructor
private ConfigManager(){}
}
(3)它必须自行向整个系统提供这个实例。外界需要获取并使用这个单例类的实例,但由于这个类的构造器是私有,外界不能new一个,那么必须提供一个静态的共有方法,该方法创建或获取它本身的静态私有对象并返回。
public class ConfigManager {
private static ConfigManager configManager;
private static Properties properties;
//private constructor
private ConfigManager(){}
//全局访问点
public static ConfigManager getInstance(){
if(configManager == null){
configManager = new ConfigManager();
}
return configManager;
}
}
3、简单示例:读取 database.properties、获取数据源的配置值,实现CRUD等等JDBC相关的操作。
(1)修改ConfigManager.java文件:
//读取配置文件的工具类-单例模式
public class ConfigManager {
private static ConfigManager configManager;
private static Properties properties;
//私有构造器-读取数据库配置文件
private ConfigManager(){
String configFile = "database.properties";
properties = new Properties();
InputStream is = ConfigManager.class.getClassLoader().getResourceAsStream(configFile);
try {
properties.load(is);
is.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//全局访问点
public static ConfigManager getInstance(){
if(configManager == null){
configManager = new ConfigManager();
}
return configManager;
}
public String getValue(String key){
return properties.getProperty(key);
}
}
(2)新建一个BaseDao类:
/**
* 操作数据库的基类--静态类
*
*/
public class BaseDao {
/**
* 获取数据库连接
* @return
*/
public static Connection getConnection(){
Connection connection = null;
String driver = ConfigManager.getInstance().getValue("driver");
String url = ConfigManager.getInstance().getValue("url");
String user = ConfigManager.getInstance().getValue("user");
String password = ConfigManager.getInstance().getValue("password");
try {
Class.forName(driver);
connection = DriverManager.getConnection(url, user, password);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return connection;
}
/**
* 查询操作
* @param connection
* @param pstm
* @param rs
* @param sql
* @param params
* @return
*/
public static ResultSet execute(Connection connection,PreparedStatement pstm,ResultSet rs,
String sql,Object[] params) throws Exception{
pstm = connection.prepareStatement(sql);
for(int i = 0; i < params.length; i++){
pstm.setObject(i+1, params[i]);
}
rs = pstm.executeQuery();
return rs;
}
/**
* 更新操作
* @param connection
* @param pstm
* @param sql
* @param params
* @return
* @throws Exception
*/
public static int execute(Connection connection,PreparedStatement pstm,
String sql,Object[] params) throws Exception{
int updateRows = 0;
pstm = connection.prepareStatement(sql);
for(int i = 0; i < params.length; i++){
pstm.setObject(i+1, params[i]);
}
updateRows = pstm.executeUpdate();
return updateRows;
}
/**
* 释放资源
* @param connection
* @param pstm
* @param rs
* @return
*/
public static boolean closeResource(Connection connection,PreparedStatement pstm,ResultSet rs){
boolean flag = true;
if(rs != null){
try {
rs.close();
rs = null;//GC回收
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
flag = false;
}
}
if(pstm != null){
try {
pstm.close();
pstm = null;//GC回收
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
flag = false;
}
}
if(connection != null){
try {
connection.close();
connection = null;//GC回收
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
flag = false;
}
}
return flag;
}
}
4、简单示例说明:
(1)BaseDao 设计为静态而非单例,因为每个线程都需要单独的Connection。
(2)弊端严重:线程不安全,会出现多个ConfigManager实例。
懒汉模式
1、懒汉模式:即在类加载时不创建实例,采用延迟加载的方式,在运行时创建实例。
2、上例就是采用了懒汉模式,在类加载时没有初始化,需要时调用了getInstance()方法,获取ConfigManager实例。虽保持了延迟加载,但无法在多线程下正常工作。现作以下修改:最简单的就是考虑同步,采用同步synchronize关键字实现。修改ConfigManager.java 文件的全局访问点:
//全局访问点
public static synchronized ConfigManager getInstance(){
if(configManager == null){
configManager = new ConfigManager();
}
return configManager;
}
3、说明:
(1)上述代码能够在多线程并发环境下很好的工作,同时具备延迟加载特性(lazy loading)。
(2)该方式效率不高,99%的情况下是不需要同步的。
饿汉模式
1、饿汉模式:类加载的时候,就完成了初始化操作,故类加载较慢,但是获取对象的速度很快。并且由于饿汉模式在类初始化时就已经自行实例化,因此肯定不存在线程安全问题。
2、实现方法:修改ConfigManager.java 文件。
public class ConfigManager {
//类加载时完成初始化操作
private static ConfigManager configManager = new ConfigManager();
private static Properties properties;
//private constructor
private ConfigManager(){
String configFile = "database.properties";
properties = new Properties();
InputStream is = ConfigManager.class.getClassLoader().getSystemResourceAsStream(configFile);
try {
properties.load(is);
is.close();
}catch(IOException e){
e.printStackTrace();
}
}
//饿汉模式的全局访问点
public static ConfigManager getInstance(){
return configManager;
}
public String getValue(String key){
//API
return properties.getProperty(key);
}
}
3、说明:
(1)需要时调用了getInstance()方法,获取ConfigManager实例,而类初始化时就已经自行实例化,这种方式基于classroader机制,有效避免多线程的同步问题。但显然没达到延迟加载特性(lazy loading)效果。
4、静态内部类方式改造:
(1)新建 Singleton.java 类:
public class Singleton {
private static Singleton singleton;
private Singleton(){
//可以添加业务代码--整个系统运行只会执行一次的业务代码操作
}
//Inner class
public static class SingletonHelper{
//同时也满足了饿汉模式,在类加载时就已经完成了初始化。
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
//调用了该方法才能获取得了实例,延迟效果达到。
singleton = SingletonHelper.INSTANCE;
return singleton;
}
public static Singleton test(){
return singleton;
}
}
(2)新建单元测试 SingletonTest.java:
public class SingletonTest {
private Logger log = Logger.getLogger(SingletonTest.class.getName());
@Test
public void test() {
log.info("Singleton.test()--->"+Singleton.test());
log.info("Singleton.getInstance()--->"+Singleton.getInstance());
}
}
(3)查看输出结果。
SpringMVC-Controller的单例管理
1、SpringMVC的Controller类默认是单例的(即scope默认是singleton)。
2、因为Controller设计为单例模式,不需要每次都创建实例,速度和性能优越。
3、在默认单例情况下,一般不要在Controller中定义成员变量。
(1)改造POJO(User.java),增加有参构造方法。
public class User {
private Integer id; //id
private String userName; //用户名称
private String userPassword; //用户密码
public User(){}
public User(Integer id,String userName,String userPassword){
this.id = id;
this.userName = userName;
this.userPassword = userPassword;
}
//省略getter/setter方法
}
(2)在UserController中增加一个成员变量,并添加查询方法,执行查询全部用户的操作。
@Controller
@RequestMapping("/user")
public class UserController{
private ArrayList<User> userList = new ArrayList<User>(); //新增成员变量
public UserController(){
try{
//初始化用户数据
userList.add(new User(1,"test001","1111111");
userList.add(new User(2,"test002","1111111");
userList.add(new User(3,"test003","1111111");
}catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
//没有查询条件的情况下,获取userList(公共查询)
@RequestMapping(value="/list",method=RequestMethod.GET)
public String list(Model model){
model.addAttribute("queryUserList",userList);
return "user/userlist";
}
}
如若转载,请注明出处:一木林多 - https://www.l5v.cn/archives/233/
评论