当前位置: 代码网 > it编程>编程语言>Java > 第十章 框架升级之单例模式及统一异常处理(2023版本IDEA)

第十章 框架升级之单例模式及统一异常处理(2023版本IDEA)

2024年08月02日 Java 我要评论
通过前面的课程学习,大家对Spring MVC 框架的整体结构和基本请求处理流程已经有了基本的了解,并可以使用Spring MVC 框架做一些简单的开发。(如果没有了解可以去我主页看看 第一至九章的内容来学习)本章将继续学习 Spring MVC 框架的基础知识,包括静态资源的访问、Servlet API 入参等,最终搭建一个 Spring MVC+Spring+JDBC 的框架,帮助大家加深对Spring MVC框架的理解。


  通过前面的课程学习,大家对spring mvc 框架的整体结构和基本请求处理流程已经有了基本的了解,并可以使用spring mvc 框架做一些简单的开发。(如果没有了解可以去我主页看看 第一至九章的内容来学习)本章将继续学习 spring mvc 框架的基础知识,包括静态资源的访问、servlet api 入参等,最终搭建一个 spring mvc+spring+jdbc 的框架,帮助大家加深对spring mvc框架的理解。

10.1 单例模式

  设计模式是一套被反复使用、多数人知晓、经过分类编目的代码设计经验的总结。使用设计模式是为了保证代码的可靠性,提高工作效率,让代码更容易被他人理解。java共有23种成熟的设计模式,今天要学习的单例模式就是其中一种。

10.1.1 使用单例模式改造数据库连接功能

  单例模式是一种比较基础的设计模式,应用非常广泛,如应用程序的日志应用,web应用的配置文件读取,数据库连接池的设计,网站的计数器等,并且springmvc框架的controller默认也是单例的。那么到底什么是单例模式?单例模式的优点又有哪些?

1. 加载配置数据

数据库配置信息存储于database.properties配置文件中,具体配置信息如下所示。

driver=com.mysql.jdbc.driver
url=jdbc:mysql://127.0.0.:3306/cvs_db?useunicode=true&characterencoding=utf8
user=root
password=123456

2. 获取数据库的连接对象

使用connection getconnection()方法获取数据库连接,返回connection实例。

3. 处理查询操作

resultset execute(connection,preparedstatement,resultset,string,object[])方法:负责根据传入的参数完成数据的查询操作,返回结果集resultset。

4. 处理增、删、改操作

使用int execute(connection,preparedstatement,resultset,string,object[])方法根据传入的参数完成数据的更新(增、删、改)操作,返回影响的行数。

5. 关闭数据库连接

使用boolean closeresource(connection,preparedstatement,resultset)方法,负责根据传入的参数进行相应资源的关闭操作,返回boolean值。

下面是一个使用单例模式改造数据库连接功能的简单示例,这里假设我们使用java语言和jdbc来连接数据库:

  1. 定义单例类 databaseconnection
import java.sql.connection;  
import java.sql.drivermanager;  
import java.sql.sqlexception;  
  
public class databaseconnection {  
    // 私有静态变量,存储唯一实例  
    private static databaseconnection instance = null;  
      
    // 私有构造函数,防止外部通过new创建实例  
    private databaseconnection() {}  
      
    // 静态方法,返回唯一实例  
    public static databaseconnection getinstance() {  
        if (instance == null) {  
            instance = new databaseconnection();  
            // 这里可以初始化数据库连接,但为了演示清晰,我们稍后在需要时调用connect方法  
        }  
        return instance;  
    }  
      
    // 数据库连接对象  
    private connection connection = null;  
      
    // 连接数据库的方法  
    public connection connect(string url, string user, string password) throws sqlexception {  
        if (connection == null || connection.isclosed()) {  
            connection = drivermanager.getconnection(url, user, password);  
        }  
        return connection;  
    }  
      
    // 关闭数据库连接的方法  
    public void close() throws sqlexception {  
        if (connection != null && !connection.isclosed()) {  
            connection.close();  
        }  
    }  
}
  1. 使用 databaseconnection
public class databasetest {  
    public static void main(string[] args) {  
        try {  
            // 获取databaseconnection的唯一实例  
            databaseconnection db = databaseconnection.getinstance();  
              
            // 使用实例连接数据库  
            string url = "jdbc:mysql://localhost:3306/yourdatabase";  
            string user = "yourusername";  
            string password = "yourpassword";  
            connection conn = db.connect(url, user, password);  
              
            // 在这里使用conn进行数据库操作...  
              
            // 操作完成后关闭连接(注意:实际使用时可能需要根据业务逻辑决定何时关闭连接)  
            // db.close();  
              
        } catch (sqlexception e) {  
            e.printstacktrace();  
        }  
    }  
}

10.1.2 懒汉模式和饿汉模式

  在10.1.1节中完成了使用单例模式改造获取数据库连接的功能,这种单例模式的实现方式称为懒汉模式。懒汉模式是懒加载的意思,即在类加载时不创建其实例,而是被调用时再创建实例。这种方式的单例模式是非线程安全的,在高并发环境下存在一个严重的问题:可能会产生多个configmanager实例。所以还需要对以上单例模式做一些改进。

在java中,单例模式的懒汉式和饿汉式是两种常见的实现方式。下面分别给出这两种模式的简单实现代码。
懒汉模式(lazy initialization)
懒汉模式的特点是类的实例在第一次被使用时才创建,即延迟加载(lazy loading)。在多线程环境下,懒汉模式需要处理线程同步问题,以确保实例的唯一性。

非线程安全的懒汉模式(不推荐在多线程环境下使用):

public class singletonlazy {  
    // 私有静态变量,存储唯一实例,初始化为null  
    private static singletonlazy instance = null;  
  
    // 私有构造函数,防止外部通过new创建实例  
    private singletonlazy() {}  
  
    // 提供一个公共的静态方法,返回唯一实例  
    // 注意:此方法在多线程环境下不安全  
    public static singletonlazy getinstance() {  
        if (instance == null) {  
            instance = new singletonlazy();  
        }  
        return instance;  
    }  
  
    // 类的其他部分...  
}

线程安全的懒汉模式(使用synchronized关键字)

public class singletonlazythreadsafe {  
    // 私有静态变量,存储唯一实例,初始化为null  
    private static singletonlazythreadsafe instance = null;  
  
    // 私有构造函数,防止外部通过new创建实例  
    private singletonlazythreadsafe() {}  
  
    // 提供一个公共的静态同步方法,返回唯一实例  
    // 使用synchronized关键字确保线程安全  
    public static synchronized singletonlazythreadsafe getinstance() {  
        if (instance == null) {  
            instance = new singletonlazythreadsafe();  
        }  
        return instance;  
    }  
  
    // 类的其他部分...  
}

双重检查锁定(double-checked locking)

为了优化性能,可以使用双重检查锁定模式,但需要注意java内存模型中的指令重排序问题,因此需要使用volatile关键字。

public class singletondoublechecked {  
    // 使用volatile关键字防止指令重排序  
    private static volatile singletondoublechecked instance = null;  
  
    // 私有构造函数,防止外部通过new创建实例  
    private singletondoublechecked() {}  
  
    // 提供一个公共的静态方法,返回唯一实例  
    public static singletondoublechecked getinstance() {  
        if (instance == null) { // 第一次检查  
            synchronized (singletondoublechecked.class) {  
                if (instance == null) { // 第二次检查  
                    instance = new singletondoublechecked();  
                }  
            }  
        }  
        return instance;  
    }  
  
    // 类的其他部分...  
}

饿汉模式(eager initialization)
饿汉模式的特点是类加载时就完成了实例的初始化,所以类加载较慢,但获取对象的速度快。由于是静态变量初始化,所以它是线程安全的。

public class singletoneager {  
    // 私有静态变量,存储唯一实例,在类加载时就完成了初始化  
    private static final singletoneager instance = new singletoneager();  
  
    // 私有构造函数,防止外部通过new创建实例  
    private singletoneager() {}  
  
    // 提供一个公共的静态方法,返回唯一实例  
    public static singletoneager getinstance() {  
        return instance;  
    }  
  
    // 类的其他部分...  
}

饿汉模式实现简单,类加载时就完成了初始化,所以它是线程安全的。但是,如果实例的创建开销很大,或者实例在程序运行过程中可能永远不会被使用,那么饿汉模式可能会造成资源浪费。

10.1.3 spring mvc 框架中的单例模式

本课程第9章中学习的spring mvc 框架中的controller默认就是单例模式的,这样的设计不需要每次都创建实例,速度和性能可能更加优秀。同时,也需要注意,因为所有的请求公用一个controller实例,所以一般不推荐在controller中定义成员变量,否则容易出现线程安全问题。

不过,为了说明目的,我将先给出一个简单的、非框架依赖的单例模式实现,然后简要说明在spring框架中如何实现类似的效果。

非框架依赖的单例模式实现
这是一个简单的饿汉式单例模式实现:

public class singletonexample {  
    // 私有静态变量,存储唯一实例  
    private static final singletonexample instance = new singletonexample();  
  
    // 私有构造函数,防止外部通过new创建实例  
    private singletonexample() {}  
  
    // 提供一个公共的静态方法,返回唯一实例  
    public static singletonexample getinstance() {  
        return instance;  
    }  
  
    // 类的其他方法...  
}

在spring框架中实现单例模式
在spring框架中,你通常不需要手动实现单例模式,因为spring容器默认就是按照单例模式来管理bean的(除非你明确指定了不同的作用域)。但是,为了说明如何在spring中“模拟”单例模式(尽管这实际上是spring的默认行为),我们可以这样看:

  1. 定义一个bean: 你只需定义一个类,并将其交给spring管理。
@component // 或者 @service, @repository 等,根据类的角色选择  
public class mybean {  
    // 类的成员和方法...  
}
  1. 配置spring以使用单例模式: 实际上,这一步是自动的,因为spring默认就是单例的。但如果你需要明确指定(尽管这通常是不必要的),你可以通过@scope注解来指定作用域。不过,对于单例模式,你不需要这么做,因为@scope(“singleton”)是默认值。
@component  
@scope("singleton") // 实际上是多余的,因为singleton是默认值  
public class mybean {  
    // 类的成员和方法...  
}
  1. 使用bean: 在spring管理的组件中,你可以通过自动装配(如使用@autowired)来获取这个bean的实例,并且确保你得到的是同一个实例。
@component  
public class mycomponent {  
  
    @autowired  
    private mybean mybean;  
  
    // 使用mybean的方法...  
}

在这个例子中,尽管我们没有显式地编写单例模式的代码,但mybean类在spring容器中被视为一个单例bean,spring确保了在整个应用程序上下文中只有一个mybean的实例被创建和共享。这就是spring框架通过依赖注入和容器管理来隐式地实现单例模式的方式。

10.2搭建项目运行环境

1. 安装java jdk
首先,你需要安装java开发工具包(jdk)。访问oracle官网 或 openjdk官网 下载适合你操作系统的jdk版本。

安装后,配置环境变量java_home指向jdk的安装目录,并将%java_home%\bin(windows)或$java_home/bin(unix/linux/macos)添加到系统的path环境变量中。

2. 安装ide或文本编辑器
你可以使用集成开发环境(ide)如intellij idea、eclipse或netbeans,或者使用文本编辑器如visual studio code、sublime text等编写java代码。

3. 搭建项目结构
使用maven或gradle等构建工具来管理项目的依赖和构建过程。以maven为例,你可以通过maven的archetype命令快速生成一个项目骨架:

mvn archetype:generate -dgroupid=com.example -dartifactid=myproject -darchetypeartifactid=maven-archetype-quickstart -dinteractivemode=false

对于spring boot项目,你可以使用spring initializr(https://start.spring.io/)来生成项目骨架,然后下载并解压,或者通过spring cli来生成。

4. 配置项目依赖
编辑pom.xml(maven)或build.gradle(gradle)文件,添加项目所需的依赖。对于spring boot项目,你通常会添加spring boot的starter依赖来简化配置。

5. 配置项目
在src/main/resources目录下,你可以添加配置文件,如application.properties或application.yml,来配置项目的数据库连接、服务器端口等。

6. 编写代码
在项目结构中创建相应的包和类,编写业务逻辑和控制器等代码。

7. 编译与运行
使用maven或gradle命令编译并运行你的项目。例如,使用maven,你可以通过以下命令来运行spring boot应用:

mvn spring-boot:run

或者使用gradle:

gradle bootrun

8. 调试与测试
使用ide的调试功能或命令行工具进行调试,并编写单元测试、集成测试来验证你的代码。

9. 部署
将编译后的应用打包成可执行的jar或war文件,并部署到服务器或云平台。

10.3 改造登录、注销功能

1. 改造登录功能
登录功能通常涉及用户提交用户名和密码,系统验证这些信息,并生成一个认证令牌(如jwt)返回给用户。

改造前(假设)
登录请求可能直接调用一个服务层的方法,该方法验证用户名和密码,并返回用户信息或状态码。

改造后
使用spring security的formloginconfigurer来配置登录表单,并自定义登录成功和失败的处理逻辑。

@configuration  
@enablewebsecurity  
public class websecurityconfig extends websecurityconfigureradapter {  
  
    @autowired  
    private userdetailsservice userdetailsservice;  
  
    @autowired  
    private passwordencoder passwordencoder;  
  
    @override  
    protected void configure(httpsecurity http) throws exception {  
        http  
            .authorizerequests()  
                .antmatchers("/login", "/register", "/error").permitall()  
                .anyrequest().authenticated()  
                .and()  
            .formlogin()  
                .loginpage("/login")  
                .permitall()  
                .successhandler(new authenticationsuccesshandler() {  
                    @override  
                    public void onauthenticationsuccess(httpservletrequest request,   
                                                        httpservletresponse response,   
                                                        authentication authentication) throws ioexception, servletexception {  
                        // 自定义登录成功后的处理逻辑,如生成jwt并返回  
                        // ...  
                        response.getwriter().write("login successful");  
                    }  
                })  
                .failurehandler(new authenticationfailurehandler() {  
                    @override  
                    public void onauthenticationfailure(httpservletrequest request,   
                                                        httpservletresponse response,   
                                                        authenticationexception exception) throws ioexception, servletexception {  
                        // 自定义登录失败后的处理逻辑  
                        // ...  
                        response.getwriter().write("login failed: " + exception.getmessage());  
                    }  
                })  
                .and()  
            .logout()  
                .permitall();  
        // 其他配置...  
    }  
  
    // 配置userdetailsservice和passwordencoder...  
}

2. 改造注销功能
注销功能通常涉及销毁用户的会话和认证信息。

改造前(假设)
注销请求可能直接调用一个服务层的方法,该方法清除用户的会话信息。

改造后
使用spring security的logoutconfigurer来配置注销url和注销后的处理逻辑。

@override  
protected void configure(httpsecurity http) throws exception {  
    http  
        // ...  
        .logout()  
            .logouturl("/logout") // 定义注销url  
            .logoutsuccessurl("/login?logout") // 注销成功后的跳转url  
            .permitall() // 允许所有用户访问注销url  
            .and()  
        // ...  
}
(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com