[学习笔记] 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/

评论