一、Web应用开发概述

1、Java Web应用分层

1、终端显示层:各个端的模板渲染并执行显示的层。当前主要是 velocity渲染,JS渲染,JSP渲染,移动端展示等,同时负责监听用户的操作,与用户进行交互。

2、请求处理层(Web层):主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。

3、业务逻辑层(Service层):相对具体的业务逻辑服务层。

4、数据持久层(DAO层):数据访问层,与底层MySQL、Oracle、HBase等进行数据交互。

web分层示例

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与SSM

二、Spring Boot框架

1、 Spring Boot概述

由Pivotal团队开发,内部集成传统三大框架,内嵌服务器(tomcat、Jetty、Undertow)可以独立运行(传统不可以),可以自动配置,性能好

spring boot框架的作用是帮助快速的构建工程,可以少写很多配置文件,可以统一依赖的版本

创建Spring Boot的方法

线上创建通过阿里云进行Spring Boot的创建,网址Cloud Native App Initializer (aliyun.com)

线下创建可以直接用idea进行创建,在创建时注意:war包需要服务器,jar包不需要服务器

Spring_Boot

<!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的运行原理

Spring Boot项目内置了一个Tomcat服务器,当启动Spring Boot项目时,实际上启动了该Tomcat服务器,默认使用本地的8080端口。

SpringBoot运行原理

2、 处理动态资源请求

(1)Controller类

用于处理来自客户端对动态资源的请求,一个Controller通常有多个HTTP端点,每个端点对应一个特定的HTTP请求路径。

Controller类

(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;
    }
}

效果展示:

Controller类示例

(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;
}

效果展示:

通过HttpServletRequest对象获取参数

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;
    }
}

效果展示:

BMI展示

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();
    }
}
最后修改:2023 年 09 月 16 日
如果觉得我的文章对你有用,请随意赞赏