• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • YouClaw
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

youyou-dev

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

Web后端基础(SpringBoot)

Web后端基础(SpringBoot)

目录
  • Web后端基础(SpringBoot)
    • 1、SpringBoot入门
    • 2. SpringBoot 核心特点
    • 3、SpringBootWeb案例
      • 3.1、开发步骤
      • 3.2、 需求说明
      • 3.3、代码实现
      • 3.4、@ResponseBody
    • 4、分层解耦
      • 4.1、三层架构
        • 4.1.1、介绍
        • 4.1.2、代码拆分
      • 4.2、分层解耦
        • 4.2.1、问题分析
        • 4.2.2、解耦思路
    • 5、IOC&DI入门
      • 5.1、IOC详解
        • 5.1.1、Bean的声明
          • 5.1.2、组件扫描
      • 5.2、DI详解
        • 5.2.1、@Autowired用法
        • 5.2.2、注意事项
    • 6、常见状态码

我们学习Web开发时,首先用 HTML、CSS、JS 这三剑客做出静态资源(图片、音视频等固定内容),把它们放到服务器上,浏览器就能访问。但静态页面不会因人而异,要实现登录、数据查询等个性化功能,就需要动态资源——后端根据请求实时生成内容(比如查询数据库返回结果)。

早期用 Servlet、JSP 实现动态资源,现在已被淘汰,企业主流是基于 Spring 框架(如 SpringBoot)来开发,并部署在 Tomcat 这类 Web 服务器中运行。浏览器与服务器通信都遵循 HTTP 协议。这种“只需浏览器、数据和逻辑全在服务端”的模式叫 BS 架构

  • BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。

    • 优点:维护方便

    • 缺点:体验一般

  • CS架构:Client/Server,客户端/服务器架构模式。需要单独开发维护客户端。

    • 优点:体验不错

    • 缺点:开发维护麻烦

1、SpringBoot入门

我们要先来了解下什么是Spring,打开Spring的官网(https://spring.io),去看一下Spring的简介:Spring makes Java simple。
Spring提供了若干个子项目,每个项目用于完成特定的功能。而我们在项目开发时,一般会偏向于选择这一套spring家族的技术,来解决对应领域的问题,那我们称这一套技术为spring全家桶。
image
Spring Boot 可以帮助我们非常快速的构建应用程序、简化开发、提高效率 。
而直接基于SpringBoot进行项目构建和开发,不仅是Spring官方推荐的方式,也是现在企业开发的主流。

2. SpringBoot 核心特点

自动配置(Auto-Configuration):根据类路径中的依赖自动配置 Spring 及第三方库(如添加 spring-boot-starter-web 自动配置 Tomcat、SpringMVC)。

起步依赖(Starter):一组预定义的依赖集合,如 spring-boot-starter-web 包含了 Web 开发常用包(无需手动组合版本)。

内嵌 Web 服务器:默认内嵌 Tomcat(也可换成 Jetty/Undertow),无需部署 WAR 包,运行 JAR 即可启动。

简化配置:application.properties 或 application.yml 代替繁琐的 XML。

关键注解

注解 作用
@SpringBootApplication 启动类注解,组合了 @Configuration + @EnableAutoConfiguration + @ComponentScan
@RestController 标记一个类为控制器,方法返回 JSON / 字符串(等价于 @Controller + @ResponseBody)
@RequestMapping 映射 URL 路径,可指定 HTTP 方法(如 @GetMapping, @PostMapping)
@Autowired 自动注入 Spring 容器中的 Bean(如 Service、Repository)
@Service / @Repository / @Component 将普通类标记为 Spring 管理的 Bean

3、SpringBootWeb案例

3.1、开发步骤

image

3.2、 需求说明

需求:基于SpringBoot开发web程序,完成用户列表的渲染展示
image

3.3、代码实现

1). 准备工作:再创建一个SpringBoot工程,并勾选web依赖、lombok依赖。
2). 准备工作:引入资料中准备好的数据文件user.txt,以及static下的前端静态页面
3). 准备工作:定义封装用户信息的实体类。
在 项目目录下再定义一个包 pojo,专门用来存放实体类。 在该包下定义一个实体类User:

package com.pojo;
//lombok.*:Lombok 是一个编译时注解库,能自动生成常用方法(如 getter/setter、构造器等),减少样板代码。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
 //封装用户信息
 //@Data:生成所有字段的 getter、setter、toString()、equals()、hashCode() 方法。
 //@NoArgsConstructor:生成一个无参数构造器。
 //@AllArgsConstructor:生成一个包含所有字段(按声明顺序)的构造器。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String username;
    private String password;
    private String name;
    private Integer age;
    private LocalDateTime updateTime;
}

4). 开发服务端程序,接收请求,读取文本数据并响应
由于在案例中,需要读取文本中的数据,并且还需要将对象转为json格式,所以,在项目中再引入一个非常常用的工具包hutool。 然后调用里面的工具类,就可以非常方便快捷的完成业务操作。

  • pom.xml中引入依赖
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.27</version>
</dependency>
  • 在com.itheima包下新建一个子包controller,在其中创建一个UserController
import cn.hutool.core.io.IoUtil;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil;
import com.itheima.pojo.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
//@RestController:组合了 @Controller 和 @ResponseBody,表示这个类中所有方法的返回值会直接写入 HTTP 响应体(通常是 JSON 或字符串),而不是跳转到视图页面。
@RestController
public class UserController {
    //@RequestMapping("/list"):将 HTTP 请求路径 /list 映射到该方法。默认接受所有 HTTP 方法(GET、POST 等),通常建议明确指定 @GetMapping 或 @PostMapping。
    @RequestMapping("/list")
    public String list(){
        //1.加载并读取文件
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");

        /*读取所有行:IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>()) 使用 Hutool 工具一次性读取输入流中的所有行,存入 ArrayList<String>。*/

        ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());

        /*lines.stream():将行集合转为 Java 8 Stream 流。
         *.map(line -> {...}):对每一行进行映射转换,将一行文本转换为一个 User 对象。
        */

        //2.解析数据,封装成对象 --> 集合
        List<User> userList = lines.stream().map(line -> {
            String[] parts = line.split(",");
            Integer id = Integer.parseInt(parts[0]);
            String username = parts[1];
            String password = parts[2];
            String name = parts[3];
            Integer age = Integer.parseInt(parts[4]);
            LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

            return new User(id, username, password, name, age, updateTime);
        }).collect(Collectors.toList());
        
        //3.响应数据
        //return JSONUtil.toJsonStr(userList, JSONConfig.create().setDateFormat("yyyy-MM-dd HH:mm:ss"));
        return userList;
    }
    
}

5). 启动服务测试,访问:http://localhost:8080/user.html
image

3.4、@ResponseBody

前面我们学习过HTTL协议的交互方式:请求响应模式(有请求就有响应)。那么Controller程序,除了接收请求外,还可以进行响应。

在我们前面所编写的controller方法中,都已经设置了响应数据。

controller方法中的return的结果,怎么就可以响应给浏览器呢?----答案:使用@ResponseBody注解

4、分层解耦

4.1、三层架构

4.1.1、介绍

在我们进行程序设计以及程序开发时,尽可能让每一个接口、类、方法的职责更单一些(单一职责原则)。

单一职责原则:一个类或一个方法,就只做一件事情,只管一块功能。

这样就可以让类、接口、方法的复杂度更低,可读性更强,扩展性更好,也更利于后期的维护。
从组成上看可以分为三个部分:

  • 数据访问:负责业务数据的维护操作,包括增、删、改、查等操作。

  • 逻辑处理:负责业务逻辑处理的代码。

  • 请求处理、响应数据:负责,接收页面的请求,给页面响应数据。

按照上述的三个组成部分,在我们项目开发中呢,可以将代码分为三层,即:

  • Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。

  • Service:业务逻辑层。处理具体的业务逻辑。

  • Dao:数据访问层(Data Access Object),也称为持久层。负责数据访问操作,包括数据的增、删、改、查。

基于三层架构的程序执行流程,如图所示:

image

  • 前端发起的请求,由Controller层接收(Controller响应数据给前端)

  • Controller层调用Service层来进行逻辑处理(Service层处理完后,把处理结果返回给Controller层)

  • Serivce层调用Dao层(逻辑处理过程中需要用到的一些数据要从Dao层获取)

  • Dao层操作文件中的数据(Dao拿到的数据会返回给Service层)

思考:按照三层架构的思想,如果要对业务逻辑(Service层)进行变更,会影响到Controller层和Dao层吗?

答案:不会影响。 (程序的扩展性、维护性变得更好了)

4.1.2、代码拆分

我们使用三层架构思想,来改造下之前的程序:

  • 控制层包名:com.itheima.controller
  • 业务逻辑层包名:com.itheima.service
  • 数据访问层包名:com.itheima.dao

1). 控制层:接收前端发送的请求,对请求进行处理,并响应数据

在 com.itheima.controller 中创建UserController类,代码如下:

package com.itheima.controller;

import com.itheima.pojo.User;
import com.itheima.service.UserService;
import com.itheima.service.impl.UserServiceImpl;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

@RestController
public class UserController {
    
    private UserService userService = new UserServiceImpl();

    @RequestMapping("/list")
    public List<User> list(){
        //1.调用Service
        List<User> userList = userService.findAll();
        //2.响应数据
        return userList;
    }

}

2). 业务逻辑层:处理具体的业务逻辑

在 com.itheima.service中创建UserSerivce接口,代码如下:

package com.itheima.service;

import com.itheima.pojo.User;
import java.util.List;

public interface UserService {

    public List<User> findAll();

}

在 com.itheima.service.``impl 中创建UserSerivceImpl接口,代码如下:

package com.itheima.service.impl;

import com.itheima.dao.UserDao;
import com.itheima.dao.impl.UserDaoImpl;
import com.itheima.pojo.User;
import com.itheima.service.UserService;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;

public class UserServiceImpl implements UserService {

    private UserDao userDao = new UserDaoImpl();

    @Override
    public List<User> findAll() {
        List<String> lines = userDao.findAll();
        List<User> userList = lines.stream().map(line -> {
            String[] parts = line.split(",");
            Integer id = Integer.parseInt(parts[0]);
            String username = parts[1];
            String password = parts[2];
            String name = parts[3];
            Integer age = Integer.parseInt(parts[4]);
            LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new User(id, username, password, name, age, updateTime);
        }).collect(Collectors.toList());
        return userList;
    }
}

3). 数据访问层:负责数据的访问操作,包含数据的增、删、改、查

在 com.itheima.dao中创建UserDao接口,代码如下:

package com.itheima.dao;

import java.util.List;

public interface UserDao {

    public List<String> findAll();

}

在 com.itheima.dao.impl 中创建UserDaoImpl接口,代码如下:

package com.itheima.dao.impl;

import cn.hutool.core.io.IoUtil;
import com.itheima.dao.UserDao;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

public class UserDaoImpl implements UserDao {
    @Override
    public List<String> findAll() {
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
        ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
        return lines;
    }
}

具体的请求调用流程:
image

三层架构的好处:

  1. 复用性强
  2. 便于维护
  3. 利用扩展

4.2、分层解耦

4.2.1、问题分析

由于我们现在在程序中,需要什么对象,直接new一个对象 new UserServiceImpl() 。如果说我们需要更换实现类,比如由于业务的变更,UserServiceImpl 不能满足现有的业务需求,我们需要切换为 UserServiceImpl2 这套实现,就需要修改Contorller的代码,需要创建 UserServiceImpl2 的实现new UserServiceImpl2() 。

Service中调用Dao,也是类似的问题。这种呢,我们就称之为层与层之间 耦合 了。 那什么是耦合呢 ?

首先需要了解软件开发涉及到的两个概念:内聚和耦合。

  • 内聚:软件中各个功能模块内部的功能联系。

  • 耦合:衡量软件中各个层/模块之间的依赖、关联的程度。

image
目前层与层之间是存在耦合的,Controller耦合了Service、Service耦合了Dao。而 高内聚、低耦合的目的是使程序模块的可重用性、移植性大大增强。
那最终我们的目标呢,就是做到层与层之间,尽可能的降低耦合,甚至解除耦合。

4.2.2、解耦思路

之前我们在编写代码时,需要什么对象,就直接new一个就可以了。 这种做法呢,层与层之间代码就耦合了,当service层的实现变了之后, 我们还需要修改controller层的代码。

那应该怎么解耦呢?

1). 首先不能在EmpController中使用new对象。代码如下:
image
此时,就存在另一个问题了,不能new,就意味着没有业务层对象(程序运行就报错),怎么办呢?

我们的解决思路是:

  • 提供一个容器,容器中存储一些对象(例:UserService对象)

  • Controller程序从容器中获取UserService类型的对象

3). 应用程序中用到这个对象,就直接从容器中获取
我们如何将对象交给容器管理呢? 程序运行时,容器如何为程序提供依赖的对象呢?

我们想要实现上述解耦操作,就涉及到Spring中的两个核心概念:

image

5、IOC&DI入门

1). 将Service及Dao层的实现类,交给IOC容器管理

在实现类加上 @Component 注解,就代表把当前类产生的对象交给IOC容器管理。

A. UserDaoImpl

@Component
public class UserDaoImpl implements UserDao {
    @Override
    public List<String> findAll() {
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
        ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
        return lines;
    }
}

B. UserServiceImpl

@Component
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    @Override
    public List<User> findAll() {
        List<String> lines = userDao.findAll();
        List<User> userList = lines.stream().map(line -> {
            String[] parts = line.split(",");
            Integer id = Integer.parseInt(parts[0]);
            String username = parts[1];
            String password = parts[2];
            String name = parts[3];
            Integer age = Integer.parseInt(parts[4]);
            LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new User(id, username, password, name, age, updateTime);
        }).collect(Collectors.toList());
        return userList;
    }
}

2). 为Controller 及 Service注入运行时所依赖的对象

A. UserServiceImpl

@Component
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;
    
    @Override
    public List<User> findAll() {
        List<String> lines = userDao.findAll();
        List<User> userList = lines.stream().map(line -> {
            String[] parts = line.split(",");
            Integer id = Integer.parseInt(parts[0]);
            String username = parts[1];
            String password = parts[2];
            String name = parts[3];
            Integer age = Integer.parseInt(parts[4]);
            LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new User(id, username, password, name, age, updateTime);
        }).collect(Collectors.toList());
        return userList;
    }
}

B. UserController

@RestController
public class UserController {
    
    @Autowired
    private UserService userService;

    @RequestMapping("/list")
    public List<User> list(){
        //1.调用Service
        List<User> userList = userService.findAll();
        //2.响应数据
        return userList;
    }

}

启动服务,运行测试。 打开浏览器,地址栏直接访问:http://localhost:8080/user.html 。 依然正常访问,就说明入门程序完成了。 已经完成了层与层之间的解耦。

5.1、IOC详解

通过IOC和DI的入门程序,我们已经基本了解了IOC和DI的基础操作。接下来呢,我们学习下IOC控制反转和DI依赖注入的细节。

5.1.1、Bean的声明

前面我们提到IOC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象。IOC容器创建的对象称为bean对象。

在之前的入门案例中,要把某个对象交给IOC容器管理,需要在类上添加一个注解:@Component

而Spring框架为了更好的标识web应用程序开发当中,bean对象到底归属于哪一层,又提供了@Component的衍生注解:

image

那么此时,我们就可以使用 @Service 注解声明Service层的bean。 使用 @Repository 注解声明Dao层的bean。 代码实现如下:

Service层:

@Service
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    @Override
    public List<User> findAll() {
        List<String> lines = userDao.findAll();
        List<User> userList = lines.stream().map(line -> {
            String[] parts = line.split(",");
            Integer id = Integer.parseInt(parts[0]);
            String username = parts[1];
            String password = parts[2];
            String name = parts[3];
            Integer age = Integer.parseInt(parts[4]);
            LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new User(id, username, password, name, age, updateTime);
        }).collect(Collectors.toList());
        return userList;
    }
}

Dao层:

@Repository
public class UserDaoImpl implements UserDao {
    @Override
    public List<String> findAll() {
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
        ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
        return lines;
    }
}

注意1:声明bean的时候,可以通过注解的value属性指定bean的名字,如果没有指定,默认为类名首字母小写。

注意2:使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller。

5.1.2、组件扫描

问题:使用前面学习的四个注解声明的bean,一定会生效吗?

答案:不一定。(原因:bean想要生效,还需要被组件扫描)

  • 前面声明bean的四大注解,要想生效,还需要被组件扫描注解 @ComponentScan 扫描。

  • 该注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解 @SpringBootApplication 中,默认扫描的范围是启动类所在包及其子包。

所以,我们在项目开发中,只需要将项目中的所有的业务类,都放在启动类所在包的子包中,就无需考虑组件扫描问题。

5.2、DI详解

依赖注入,是指IOC容器要为应用程序去提供运行时所依赖的资源,而资源指的就是对象。

在入门程序案例中,我们使用了@Autowired这个注解,完成了依赖注入的操作,而这个Autowired翻译过来叫:自动装配。

@Autowired注解,默认是按照类型进行自动装配的(去IOC容器中找某个类型的对象,然后完成注入操作)

入门程序举例:在EmpController运行的时候,就要到IOC容器当中去查找EmpService这个类型的对象,而我们的IOC容器中刚好有一个EmpService这个类型的对象,所以就找到了这个类型的对象完成注入操作。

5.2.1、@Autowired用法

@Autowired 进行依赖注入,常见的方式,有如下三种:

1). 属性注入

@RestController
public class UserController {

    //方式一: 属性注入
    @Autowired
    private UserService userService;
    
  }
  • 优点:代码简洁、方便快速开发。

  • 缺点:隐藏了类之间的依赖关系、可能会破坏类的封装性。

2). 构造函数注入

@RestController
public class UserController {

    //方式二: 构造器注入
    private final UserService userService;
    
    @Autowired //如果当前类中只存在一个构造函数, @Autowired可以省略
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
 }   
  • 优点:能清晰地看到类的依赖关系、提高了代码的安全性。

  • 缺点:代码繁琐、如果构造参数过多,可能会导致构造函数臃肿。

  • 注意:如果只有一个构造函数,@Autowired注解可以省略。(通常来说,也只有一个构造函数)

3). setter注入

/**
 * 用户信息Controller
 */
@RestController
public class UserController {
    
    //方式三: setter注入
    private UserService userService;
    
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    
}    
  • 优点:保持了类的封装性,依赖关系更清晰。

  • 缺点:需要额外编写setter方法,增加了代码量。

在项目开发中,基于@Autowired进行依赖注入时,基本都是第一种和第二种方式。(官方推荐第二种方式,因为会更加规范)但是在企业项目开发中,很多的项目中,也会选择第一种方式因为更加简洁、高效(在规范性方面进行了妥协)。

5.2.2、注意事项

那如果在IOC容器中,存在多个相同类型的bean对象,会出现什么情况呢?

项目报错,出现错误的原因是因为在Spring的容器中,UserService这个类型的bean存在两个,框架不知道具体要注入哪个bean使用,所以就报错了。

Spring提供了以下几种解决方案:

  • @Primary

  • @Qualifier

  • @Resource

方案一:使用@Primary注解

当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现。

@Primary
@Service
public class UserServiceImpl implements UserService {
}

方案二:使用@Qualifier注解

指定当前要注入的bean对象。 在@Qualifier的value属性中,指定注入的bean的名称。 @Qualifier注解不能单独使用,必须配合@Autowired使用。

@RestController
public class UserController {

    @Qualifier("userServiceImpl")
    @Autowired
    private UserService userService;

方案三:使用@Resource注解

是按照bean的名称进行注入。通过name属性指定要注入的bean的名称。

@RestController
public class UserController {
        
    @Resource(name = "userServiceImpl")
    private UserService userService;

面试题:@Autowird 与 @Resource的区别

  • @Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解

  • @Autowired 默认是按照类型注入,而@Resource是按照名称注入

6、常见状态码

image

  • 状态码大全:https://cloud.tencent.com/developer/chapter/13553

以上笔记内容来自黑马程序员

posted on 2026-04-09 19:08  U~U  阅读(3)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3