一、Web应用开发概述
1、Java Web应用分层
1、终端显示层:各个端的模板渲染并执行显示的层。当前主要是 velocity渲染,JS渲染,JSP渲染,移动端展示等,同时负责监听用户的操作,与用户进行交互。
2、请求处理层(Web层):主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。
3、业务逻辑层(Service层):相对具体的业务逻辑服务层。
4、数据持久层(DAO层):数据访问层,与底层MySQL、Oracle、HBase等进行数据交互。
2、Java流行框架
1、Spring:是一个轻量级的框架,可以通过控制反转(IOC)和面向切面编程(AOP)实现松耦合和更好的代码可重用性。
2、Spring MVC:是基于Spring实现的一个MVC框架,主要用于Web开发,提供了方便的请求处理机制、灵活的数据验证机制。
3、MyBatis:是一个支持定制化SQL、存储过程和高级映射的持久化框架,提供了按需加载等高级映射功能,减少了大型项目的开发者代码量。
4、Hibernate框架(了解):Hibernate是一个开源的Java对象关系映射框架,提供了面向对象编程的方式来访问数据库。
5、Apache Struts 2(了解):是一个基于Java的Web应用程序框架,它使用标准的Java Web技术,以及Java、Groovy编程语言。Struts 2提供了许多好用的特性,例如拦截器、表单验证、代理机制等等。
3、SSM三大架构
Spring、Spring MVC、MyBatis,但是三者的连接很麻烦,那么引进了Spring Boot取代传统的三大框架集成方式
二、Spring Boot框架
1、 Spring Boot概述
由Pivotal团队开发,内部集成传统三大框架,内嵌服务器(tomcat、Jetty、Undertow)可以独立运行(传统不可以),可以自动配置,性能好
spring boot框架的作用是帮助快速的构建工程,可以少写很多配置文件,可以统一依赖的版本
创建Spring Boot的方法
线上创建通过阿里云进行Spring Boot的创建,网址Cloud Native App Initializer (aliyun.com)
线下创建可以直接用idea进行创建,在创建时注意:war包需要服务器,jar包不需要服务器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>
Hello Spring Boot!!!
</h1>
</body>
</html>
效果展示:
Spring Boot的运行原理
Spring Boot项目内置了一个Tomcat服务器,当启动Spring Boot项目时,实际上启动了该Tomcat服务器,默认使用本地的8080端口。
2、 处理动态资源请求
(1)Controller类
用于处理来自客户端对动态资源的请求,一个Controller通常有多个HTTP端点,每个端点对应一个特定的HTTP请求路径。
(2)Controller类中的注解
1、@Controller:添加于类前,将该类声明为Spring Boot的Controller类。
2、@RequestMapping:添加于类前或方法前,声明该类或方法映射的HTTP请求路径
- @GetMapping、@PostMapping、@PutMapping、@DeleteMapping与@RequestMapping功能相似,但是限定了用户的请求方式
3、@ResponseBody:添加于方法前,声明将方法的返回值作为响应数据发送给用户
4、@RestController:添加于类前,声明该类为Controller类,并为类中的每个方法添加@ResponseBody注解(组合注解)
/**
* @document: 定义一个控制器类
* @Author:SmallG
* @CreateTime:2023/9/2+14:18
*/
@Controller //声明这个类是控制器类
public class HelloController {
//地址必须是/开头,因为请求的时候就是以/开头
@RequestMapping("/hello")
@ResponseBody //声明以方法的返回值内容作为给客户端的响应内容
public String hello() {
System.out.println("收到了客户端的请求,正在处理...");
//获取系统时间并转换为字符串
String now = LocalDateTime.now().toString();
//返回系统时间
return "服务器的时间:" + now;
}
}
效果展示:
(3)接收HTTP请求参数
接收的三种方式:
1、通过HttpServletRequest对象获取参数
@RequestMapping("/p1")
@ResponseBody
public String p1(HttpServletRequest request) {
//接收请求中的参数
String name = request.getParameter("name");
String age = request.getParameter("age");
System.out.println("接收到用户请求的参数:" + name + ":" + age);
return "接收到用户的请求参数:name=" + name + ",age=" + age;
}
效果展示:
2、通过在方法的参数列表处声明的方式接受参数,适合传递少量的参数,如果很多参数使用第三种
@RequestMapping("/p2")
@ResponseBody
public String p2(String name, Integer age) {
//以方法参数列表的形式接收请求参数,并且可以自动完成数据的类型转换
//优点是如果age出现非数字字符会出现400:客户端请求语法错误,服务器不能理解
System.out.println("接收到用户请求的参数:" + name + ":" + age);
return "接收到用户的请求参数:name=" + name + ",age=" + age;
}
3、(推荐使用)☆☆通过在方法的参数列表传递包含响应参数的对象
@RequestMapping("/p3")
@ResponseBody
public String p3(Person person) {
//会通过反射得到对象的属性 然后检查传递的参数是否有同名的参数
//如果有同名的参数那么会调用相应的set方法完成对象属性赋值
System.out.println("p3接收到客户端请求参数:" + person);
return person.toString();
}
/**
* @document: person实体类
* @Author:SmallG
* @CreateTime:2023/9/2+15:08
*/
public class Person {
private String name;
private Integer age;
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
(4)BMI计算器示例
bmi.html中的内容如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>BMI测试</title>
</head>
<body>
<form action="/bmi" method="post">
<input type="text" name="h" placeholder="请输入身高(米)"><br><br>
<input type="text" name="w" placeholder="请输入体重(公斤)"><br><br>
<input type="submit" value="开始测试"><br><br>
</form>
</body>
</html>
BMIController中的内容如下所示:
/**
* @document: 用于计算BMI指数 BMI = 体重/(身高的平方)
* BMI < 18.5 ---偏瘦
* BMI >= 19.5 && BMI < 24 --正常
* 24 <= BMI < 28 --超重
* BMI >= 28 --肥胖
* @Author:SmallG
* @CreateTime:2023/9/2+15:23
*/
@Controller
public class BMIController {
@RequestMapping("/bmi")
@ResponseBody
public String BMI(Double h, Double w) {
Double bmi = w / (h * h);
String message = "您的bmi为:" + bmi;
if (bmi < 18.5) {
message += "体型偏瘦";
}
if (bmi >= 18.5 && bmi < 24) {
message += "身体健康";
}
if (bmi >= 24 && bmi < 28) {
message += "有点胖了";
}
if (bmi >= 28) {
message += "肥胖了,该运动了";
}
return message;
}
}
效果展示:
3、注册案例
(1)创建数据库,建表
CREATE DATABASE IF NOT EXISTS `coolsharkhub` DEFAULT CHARACTER SET utf8mb4;
USE `coolsharkhub`;
CREATE TABLE IF NOT EXISTS `user` (
`id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '用户id',
`username` varchar(50) DEFAULT NULL COMMENT '用户名',
`password` varchar(50) DEFAULT NULL COMMENT '密码',
`role` varchar(50) DEFAULT NULL COMMENT '角色',
`created` datetime NOT NULL DEFAULT now() COMMENT '创建时间',
`updated` datetime NOT NULL DEFAULT now()
ON UPDATE now() COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COMMENT='用户';
INSERT INTO `user` (`id`, `username`, `password`, `role`) VALUES
(1, 'admin', '123456', '管理员');
(2)在项目的pom文件中添加对数据库操作的依赖
<!-- 数据库驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
<!-- 德鲁伊数据连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
(3)DBUtil数据库工具类
public class DBUtil {
//阿里提供的连接池(重复使用连接,管理连接数量)
private static DruidDataSource dds;
static {
init();
}
private static void init(){
dds = new DruidDataSource();//实例化连接池
//设置数据库用户名,密码。最大连接数,初始连接数。连接数据的URL
dds.setUsername("root");
dds.setPassword("root");
dds.setUrl("jdbc:mysql://localhost:3306/coolsharkhub?" +
"characterEncoding=utf8&useSSL=false" +
"&serverTimezone=Asia/Shanghai");
dds.setInitialSize(10);//初始连接数
dds.setMaxActive(20);//最大连接数
}
public static Connection getConnection() throws SQLException {
return dds.getConnection();
}
}
(4)创建用户实体类
/**
* @document:
* 对应数据库表user
* 类中的属性对相应表中的字段
* @Author:SmallG
* @CreateTime:2023/9/2+16:07
*/
public class User {
private Integer id;
private String username;
private String password;
private String role;
private Date created;
private Date update;
get、set方法
构造方法(无参、有参)
toString方法
}
(5)创建Dao层
这里省略的接口
/**
* @document:
* @Author:SmallG
* @CreateTime:2023/9/2+16:21
*/
public class UserDaoImpl implements UserDao {
@Override
public int register(User user) {
int result = 0;
try (
Connection connection = DBUtil.getConnection();
) {
String sql = "INSERT INTO user(username,password,role)" +
"VALUES(?,?,?)";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1, user.getUsername());
ps.setString(2, user.getPassword());
ps.setString(3, user.getRole());
result = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
(6)创建Service层
Service层接口
/**
* @document: 用户业务层接口
* 方法除查询外没有返回值
* @Author:SmallG
* @CreateTime:2023/9/2+16:34
*/
public interface UserService {
/**
* 用户注册业务处理方法
* @param user 用户对象
*/
void register(User user);
}
接口的实现类(省略自定义异常)
public class UserServiceImpl implements UserService {
//以后要用Spring 现在这个先这么写
UserDao userDao = new UserDaoImpl();
@Override
public void register(User user) {
//对数据验证 用户名不能为空
if (StringUtils.hasText(user.getUsername())) {
throw new UserException("注册失败:用户名不能为空");
}
if (user.getUsername().length() < 4) {
throw new UserException("注册失败,用户名不能低于四位");
}
if (StringUtils.hasText(user.getPassword())) {
throw new UserException("注册失败,密码不能为空");
}
if (user.getPassword().length() < 6) {
throw new UserException("注册失败,密码不能低于6位");
}
user.setRole("普通用户");
//保存用户信息
int result = userDao.register(user);
if (result < 1) {
throw new UserException("注册失败,注册出现异常");
}
}
}
(7)编写controller层
@Controller
public class UserController {
//创建UserService对象,现在先这么用着,以后用别的
UserService userService = new UserServiceImpl();
//注册方法只接收POST请求
@RequestMapping(value = "/register", method = RequestMethod.POST)
@ResponseBody
public String register(User user) {
//保存用户信息
try {
userService.register(user);
} catch (UserException e) {
String message = e.getMessage();
//返回异常信息
return message;
}
return "注册成功";
}
}
测试(test.html):
POST http://localhost:8080/user/register
Content-Type: application/x-www-form-urlencoded
username=To123m&password=123456
###
4、Spring Boot请求参数校验
使用Spring Boot请求参数校验的基本步骤如下:
1、引入依赖:添加spring-boot-starter-validation依赖,以引入校验功能。
<!-- Spring Boot参数校验依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2、定义校验规则:在实体类的相应属性上添加注解,用于校验,
@NotBlank(message = "用户名不能为空")
//用户名不能为空,如果为空会抛出message信息
@Size(min = 4, max = 20, message = "用户名长度范围是4~20")
//用户名超出范围抛异常信息
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码的长度范围是6~20")
private String password;
3、控制器中应用校验:在controller上添加@Valid注解(参数需要通过校验)
4、处理校验错误:可以使用BindingResult对象获取详细的校验错误信息,然后将错误信息返回给客户端。也可以在控制器方法中捕获MethodArgumentNotValidException异常,并从中提取校验错误信息。
//注册方法只接收POST请求
@RequestMapping(value = "/user/register", method = RequestMethod.POST)
@ResponseBody
public String register(@Valid User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
String error = bindingResult.getFieldError().getDefaultMessage();
return error; //返回错误的消息
}
//保存用户信息
try {
userService.register(user);
} catch (UserException e) {
String message = e.getMessage();
//返回异常信息
return message;
}
return "注册成功";
}
5、自定义验证注解
/**
* @document: 自定义一个注解,用于校验用户名不能重复
* @Author:SmallG
* @CreateTime:2023/9/2+18:09
*/
//注解只能用在属性上
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) //注解的作用范围是任何时候
@Constraint(validatedBy = UniqueUsernameValidator.class) //指定注解的校验器
public @interface UniqueUsername {
//定义一个属性 名为message 默认值是:用户名已存在
String message() default "用户名已存在";
Class<?>[] groups() default {}; //用于分组校验
//payload用于接收异常信息
Class<? extends Payload>[] payload() default {}; //用于展示验证失败的信息
}
注解校验器:
/**
* @document: 注解校验器 用于校验自定义的注解
* @Author:SmallG
* @CreateTime:2023/9/2+18:19
*/
@Component //交给Spring管理
//需要实现一个接口ConstraintValidator,第一个参数的自定义的注解,第二个是出现了异常的数据类型
public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, String> {
@Override
public void initialize(UniqueUsername constraintAnnotation) { //用于初始化
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(String username, ConstraintValidatorContext constraintValidatorContext) {
//用户名为空 不验证 isblank--->是否为空
if (StringUtils.isBlank(username)) {
return true; //跳过 不验证
}
//连接数据库查询用户名是否存在
try (Connection connection = DBUtil.getConnection();) {
String sql = "SELECT COUNT(*) FROM user WHERE username=?";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1, username);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
if (rs.getInt(1) > 0) {
//如果查询的数量>0说明用户名存在
return false; //返回false说明出现了异常
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return true; //没有查询到数据,说明用户名不存在,验证成功
}
}
给实体类中username加上注解后,运行效果展示:
三、练习
1 添加商品用例
在Homework项目下新建一个Module,该Module为一个Spring Boot项目,命名为ssm_hw01,在该项目中实现添加商品用例。具体要求:
1、商品需要包含id,title, price和num共4个属性,其中id和num为整型,title为字符串型,price为浮点型
2、创建数据库ssm_hw01db,并在该库下新建product表,product表字段根据要求1进行设计
3、项目中的包名、类名、静态资源文件名称如图所示:
4、类和文件的具体功能如下:
- addProduct.html:提供添加一个商品信息的表单
- ProductController:处理商品相关请求的控制器类,其中包含一个addProduct方法,该方法用于接收用户提交的商品数据,并存储到数据库中,并向用户返回添加结果
- Product:封装商品信息的实体类
- DBUtil:数据库连接池工具类
提示:DBUtil可以复用之前的代码,但是需要注意修改数据库相关的配置。
静态文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加商品</title>
</head>
<body>
<h1>添加商品页面</h1>
<form action="/addProduct">
<input type="text" name="title" placeholder="标题">
<input type="text" name="price" placeholder="价格">
<input type="text" name="num" placeholder="库存">
<input type="submit" value="添加">
</form>
</body>
</html>
商品实体类:
package com.example.ssm_hw01.entity;
/**
* @document: 商品实体类
* @Author:SmallG
* @CreateTime:2023/9/2+19:01
*/
public class Product {
private Integer id;
private String title;
private Double price;
private Integer num;
public Product() {
}
public Product(Integer id, String title, Double price, Integer num) {
this.id = id;
this.title = title;
this.price = price;
this.num = num;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
@Override
public String toString() {
return "Product{" +
"id=" + id +
", title='" + title + '\'' +
", price=" + price +
", num=" + num +
'}';
}
}
controller层:
@Controller
public class ProductController {
@RequestMapping("/addProduct")
@ResponseBody
public String addProduct(Product product) {
int result = 0;
//获取数据库连接
try (
Connection connection = DBUtil.getConnection()
) {
String sql = "INSERT INTO product VALUES(null,?,?,?)";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1, product.getTitle());
ps.setDouble(2, product.getPrice());
ps.setInt(3, product.getNum());
result = ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
if (result > 0) {
return "添加成功,名称:" + product.getTitle() + ",价格:" + product.getPrice() + ",数量:" + product.getNum();
} else {
return "添加失败";
}
}
}
工具类(连接池)
public class DBUtil {
//阿里提供的连接池(重复使用连接,管理连接数量)
private static DruidDataSource dds;
static {
init();
}
private static void init(){
dds = new DruidDataSource();//实例化连接池
//设置数据库用户名,密码。最大连接数,初始连接数。连接数据的URL
dds.setUsername("root");
dds.setPassword("root");
dds.setUrl("jdbc:mysql://localhost:3306/ssm_hw01db?" +
"characterEncoding=utf8&useSSL=false" +
"&serverTimezone=Asia/Shanghai");
dds.setInitialSize(10);//初始连接数
dds.setMaxActive(20);//最大连接数
}
public static Connection getConnection() throws SQLException {
return dds.getConnection();
}
}