学习目标
通过前面的课程学习,大家对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来连接数据库:
- 定义单例类 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();
}
}
}
- 使用 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的默认行为),我们可以这样看:
- 定义一个bean: 你只需定义一个类,并将其交给spring管理。
@component // 或者 @service, @repository 等,根据类的角色选择
public class mybean {
// 类的成员和方法...
}
- 配置spring以使用单例模式: 实际上,这一步是自动的,因为spring默认就是单例的。但如果你需要明确指定(尽管这通常是不必要的),你可以通过@scope注解来指定作用域。不过,对于单例模式,你不需要这么做,因为@scope(“singleton”)是默认值。
@component
@scope("singleton") // 实际上是多余的,因为singleton是默认值
public class mybean {
// 类的成员和方法...
}
- 使用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()
// ...
}
发表评论