Web基础

1.SpringBoot
Spring
-
官网:spring.io
-
Spring发展到今天已经形成了一种开发生态圈,Spring提供了若干个子项目,每个项目用于完成特定的功能。
![image]()
Spring Boot可以帮助我们非常快速的构建应用程序、简化开发、提高效率。
![image]()
1.1、入门程序
- 需求:基于SpringBoot开发一个Web应用,浏览器发起请求/hello之后,给浏览器返回一个字符串“Hello Xxx”。


创建完Spring Boot项目后

这个为启动类,启动后才可以运行项目
创建如上图HelloController的请求类
package com.example.springbootwebquickstart;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController //表示当前是一个请求处理类
public class HelloController {
@RequestMapping("/hello")
public String hello(String name){
System.out.println("name:" + name);
return "Hello" + name + "~";
}
}
然后访问


步骤:
- 创建SpringBoot工程,勾选Web开发依赖
- 定义请求处理类HelloController,定义请求处理方法
- 运行启动类,测试

1.2、HTTP协议
-
概念:Hyper Text Transfer Protocol,超文本传输协议,规定了浏览器和服务器之间数据传输的规则。
![image]()
我们回到上一小节我们请求 Helloheima~

可以看到浏览器向客户端发出的请求和客户端的响应都是纯文本格式
- 特点
- 基于TCP协议:面向连接,安全
- 基于请求-响应模型的:一次请求对应一次响应
- HTTP协议是无状态的协议:对于事务处理没有记忆能力。每次请求-响应都是独立的。
- 缺点:多次请求间不能共享数据
- 优点:速度快
HTTP-请求协议
请求数据格式


HTTP协议中请求数据分为哪几个部分?
- 请求行(请求数据的第一行)
- 请求头(key:value)
- 请求体(POST方式 与请求头之间隔了一个空行)
请求数据获取
-
Web服务器(Tomcat)对HTTP协议的请求数据进行解析,并进行封装(HttpServletRequest),在调用Controller方法的时候传递给了该方法。这样,就使得程序员不必直接对协议进行操作,让Web开发更加便捷。
![image]()
@RequestMapping("/request")
public String request(HttpServletRequest request){
// 1.获取请求参数name, age
String name = request.getParameter("name"); // Tom
// 2.获取请求路径uri 和 url
String uri = request.getRequestURI(); // /request
String url = request.getRequestURL().toString(); // http://localhost:8080/request
// 3.获取请求头 User-Agent
String userAg ent = request.getHeader("User-Agent"); // Mozilla/5.0 (Windows NT 10.0; Win64; x64)
// 4.获取请求方式
String method = request.getMethod(); // GET
// 5.获取请求的查询字符串
String queryString = request.getQueryString(); // name=Tom&age=10
return "request success";
}
我们可以创建一个RequestController类来测试一下:
package com.example.springbootwebquickstart;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RequestController {
@RequestMapping("/request")
public String request(HttpServletRequest request){
//1.获取请求方式
String method = request.getMethod();
System.out.println("请求方式:" + method);
//2.获取请求url地址
String url = request.getRequestURL().toString();
System.out.println("请求地址:" + url);
//3.获取请求协议
String protocol = request.getProtocol();
System.out.println("请求协议:" + protocol);
//4.获取请求参数- name,age
String name = request.getParameter("name");
String age = request.getParameter("age");
System.out.println("请求参数:" + name + " " + age);
//5.获取请求头 - Accept
String Accept = request.getHeader("Accept");
System.out.println("请求头:" + Accept);
return "请求成功";
}
}
请求代码可以ai生成
当我们在浏览器中输入网址 localhost:8080/request?name=itheima&age=18



HTTP-相应协议
响应数据格式


关于重定向:

浏览器会进行两次请求行为,但对于用户来说没有感知
比如百度地址为(https://www.baidu.com/) 如果我们输入(http://www.baidu.com/)就会发生重定向

需掌握的状态码:

常见状态码错误:

响应数据设置
- Web服务器对HTTP协议的响应数据进行了封装(HttpServletResponse),并在调用Controller方法的时候传递给了该方法。这样,就使得程序员不必直接对协议进行操作,让Web开发更加便捷。

有两种方式:

方式一:
package com.example.springbootwebquickstart;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@RestController
public class ResponseController {
/**
* 方式一:HttpServletResponse 设置响应数据
* @param response
* @return
*/
@RequestMapping("/response")
public void response(HttpServletResponse response) throws IOException {
//1. 设置响应状态码
response.setStatus(401); //通常不用设定
//2.设置响应头
response.setHeader("name","itheima");
//3.设置响应体
response.getWriter().print("<h1>hello response</h1>");
}
}

方式二
package com.example.springbootwebquickstart;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@RestController
public class ResponseController {
/**
* 方式一:HttpServletResponse 设置响应数据
* @param response
* @return
*/
@RequestMapping("/response")
public void response(HttpServletResponse response) throws IOException {
//1. 设置响应状态码
response.setStatus(401); //通常不用设定
//2.设置响应头
response.setHeader("name","itheima");
//3.设置响应体
response.getWriter().print("<h1>hello response</h1>");
}
/**
* 方式二:使用ResponseEntity -Spring中提供的方式
* @return
*/
@RequestMapping("/response2")
public ResponseEntity<String> response2(){
return ResponseEntity
.status(401) //响应状态码
.header("name","javaweb-ai") //响应头
.body("<h1>hello responseEntity</h1>"); //响应体
}
}

注意:响应状态码和响应头如果没有特殊要求的话,通常不手动设定。服务器会根据请求处理的逻辑,自动设置响应状态码和响应头。

1.3、Spring Boot Web案例

-
准备工作
-
创建一个SpringBoot工程,并勾选web依赖、lombok。
![image]()
![image]()
-
引入资料中准备好的用户数据文件(user.txt),及前端静态页面
![image]()
-
定义一个实体类,用来封装用户信息
package com.example.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.time.LocalDateTime; /** * 封装用户信息 */ @Data //自动生成所有字段的getter/setter方法。 @NoArgsConstructor @AllArgsConstructor public class User { private Integer id; private String username; private String password; private String name; private Integer age; private LocalDateTime updateTime; }
-
-
开发服务端程序,接受请求,读取文本数据并响应。
由于在案例中,需要读取文本中的数据,并且还需要将对象转为json格式,所以这里呢,我们在项目中再引入一个非常常用的工具包hutool。 然后调用里面的工具类,就可以非常方便快捷的完成业务操作。
-
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.27</version> </dependency> -
在
com.example包下新建一个子包controller,在其中创建一个UserControllerpackage com.example.controller; import cn.hutool.core.io.IoUtil; import com.example.pojo.User; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.FileInputStream; import java.io.FileNotFoundException; 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.Stream; /** * 用户信息Controller */ @RestController //@ResponseBody ->作用:将controller方法返回的数据直接作为 响应数据返回给浏览器;返回值:json public class UserController { @RequestMapping("/list") public List<User> list() throws Exception { //1. 加载并读取user.txt文件,获取用户数据 InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt"); ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>()); //2. 解析用户数据,封装为User对象->list集合 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); }).toList(); //3. 返回数据(json) return userList; } } -
启动服务测试****,访问:http://localhost:8080/user.html
![image]()
在相应文件中可见已经转为json
![image]()
-
1.4、分层解耦
分层

由于我们把读取数据 封装数据 响应数据都写在了同一个类中,这样就使得代码的复用性差且难以维护。
-
三层架构
![image]()
![image]()
- controller:控制层,接受前端发送的请求,对请求进行处理,并响应数据。
- service:业务逻辑层,处理具体的业务逻辑。
- dao:数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增、删、改、查。
下面我们把之前写的代码进行分三层架构:
我们要明白一开始前端发送请求会先到controller层,controller层接受请求,响应数据,然后会调用service层进行业务逻辑处理,在处理过程中,会访问到数据这时候就需要dao层来获取数据,然后将数据传递给service层,等service层将数据处理好,在返回给controller层,最后controller层再返回给前端。
- 首先创建dao包,里面有UserDao接口和ipml包(其中有对应接口的实现类)
- 同样创建service包,里面有UserService接口和ipml包(其中有对应接口的实现类)

如图这样controller,dao,service三层架构,然后将原本存放在UserController中的代码进行分离
在UserDao接口和UserService接口中定义findAll()方法
在其实现类中填入分离后的对应代码
UserDaoImpl中
package com.example.dao.impl;
import cn.hutool.core.io.IoUtil;
import com.example.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() {
//1. 加载并读取user.txt文件,获取用户数据
InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
return lines;
}
}
UserServiceImpl中
package com.example.service.impl;
import com.example.dao.UserDao;
import com.example.dao.impl.UserDaoImpl;
import com.example.pojo.User;
import com.example.service.UserService;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
public List<User> findAll(){
List<String> lines = userDao.findAll();
//2. 解析用户数据,封装为User对象->list集合
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);
}).toList();
return userList;
}
}
UserController中
package com.example.controller;
import com.example.pojo.User;
import com.example.service.UserService;
import com.example.service.impl.UserServiceImpl;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController //@ResponseBody ->作用:将controller方法返的数据直接作为 响应数据返回给浏览器;返回值:json
public class UserController {
private UserService userService = new UserServiceImpl();
@RequestMapping("/list")
public List<User> list() throws Exception {
//1. 调用service方法,获取用户数据
List<User> userList = userService.findAll();
//2. 返回数据(json)
return userList;
}
}

解耦
- 耦合:衡量软件中各个层/各个模块的依赖关联程度。
- 内聚:软件中各个功能模块内部的功能联系。
- 软件设计原则:高内聚低耦合。
由于我们现在在程序中,需要什么对象,直接new一个对象 new UserServiceImpl() 。

如果说我们需要更换实现类,比如由于业务的变更,UserServiceImpl 不能满足现有的业务需求,我们需要切换为 UserServiceImpl2 这套实现,就需要修改Contorller的代码,需要创建 UserServiceImpl2 的实现new UserServiceImpl2() 。

这样的话,由于controller层调用了service层的实现类,service和controller层中的代码都需要修改,这种呢,我们就称之为层与层之间 耦合 了。
-
解耦思路
1). 首先不能在EmpController中使用new对象。代码如下:
@RestController public class UserController { private UserService userService; @RequestMapping("/list") public List<User> list(){ //1.调用Service List<User> userList = userService.findAll(); //2.响应数据 return userList; } }此时,就存在另一个问题了,不能new,就意味着没有业务层对象(程序运行就报错),怎么办呢?
我们的解决思路是:
- 提供一个容器,容器中存储一些对象(例:UserService对象)
- Controller程序从容器中获取UserService类型的对象
2). 将要用到的对象交给一个容器管理。
![image]()
3). 应用程序中用到这个对象,就直接从容器中获取

那问题来了,我们如何将对象交给容器管理呢? 程序运行时,容器如何为程序提供依赖的对象呢?
我们想要实现上述解耦操作,就涉及到Spring中的两个核心概念:
-
控制反转: Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。
- 对象的创建权由程序员主动创建转移到容器(由容器创建、管理对象)。这个容器称为:IOC容器或Spring容器。
-
依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。
- 程序运行时需要某个资源,此时容器就为其提供这个资源。
- 例:EmpController程序运行时需要EmpService对象,Spring容器就为其提供并注入EmpService对象。
-
bean对象:IOC容器中创建、管理的对象,称之为:bean对象。
1.5、IOC & DI 入门
其实就涉及到两个核心注解
@Component //将UserService类加入到容器中
public class UserServiceImpl implements UserService {
@Autowired //从容器中查询UserDao,并赋值给userDao
private UserDao userDao;
@Component //将UserDao类加入到容器中
public class UserDaoImpl implements UserDao {
简单来说,当我们创建对应接口的实现类时,使用@Component注解将对应类加入到IOC容器中,这样当我们在其他层调用时,就不需要new对象了,直接使用@Autowired注解从IOC容器中查找bean对象,注入依赖(DI)
1.6、IOC详解
通过IOC和DI的入门程序呢,我们已经基本了解了IOC和DI的基础操作。接下来呢,我们学习下IOC控制反转和DI依赖注入的细节。
Bean的声明
前面我们提到IOC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象。IOC容器创建的对象称为bean对象。
在之前的入门案例中,要把某个对象交给IOC容器管理,需要在类上添加一个注解:@Component
而Spring框架为了更好的标识web应用程序开发当中,bean对象到底归属于哪一层,又提供了@Component的衍生注解:

我们依然可以在控制层,业务层,数据访问层使用@Component注解,但并不规范
注意1:声明bean的时候,可以通过注解的value属性指定bean的名字,如果没有指定,默认为类名首字母小写。
注意2:使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller。
组件扫描
问题:使用前面学习的四个注解声明的bean,一定会生效吗?
答案:不一定。(原因:bean想要生效,还需要被组件扫描)
- 前面声明bean的四大注解,要想生效,还需要被组件扫描注解
@ComponentScan扫描。 - 该注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解
@SpringBootApplication中,默认扫描的范围是启动类所在包及其子包。

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

1.7、DI详解
依赖注入,是指IOC容器要为应用程序去提供运行时所依赖的资源,而资源指的就是对象。
在入门程序案例中,我们使用了@Autowired这个注解,完成了依赖注入的操作,而这个Autowired翻译过来叫:自动装配。
@Autowired注解,默认是按照类型进行自动装配的(去IOC容器中找某个类型的对象,然后完成注入操作)
入门程序举例:在EmpController运行的时候,就要到IOC容器当中去查找EmpService这个类型的对象,而我们的IOC容器中刚好有一个EmpService这个类型的对象,所以就找到了这个类型的对象完成注入操作。

@RestController //@ResponseBody ->作用:将controller方法返的数据直接作为 响应数据返回给浏览器;返回值:json
public class UserController {
//方式一:属性注入
@Autowired
private UserService userService;
// //方式二:构造方法注入
// private final UserService userService;
// @Autowired //如果当前类只存在一个构造方法,则@Autowired可以省略 一般都只存在一个构造方法
// public UserController(UserService userService) {
// this.userService = userService;
// }
// //方式三:set方法注入
// private UserService userService;
// @Autowired
// public void setUserService(UserService userService) {
// this.userService = userService;
// }
@RequestMapping("/list")
public List<User> list() throws Exception {
//1. 调用service方法,获取用户数据
List<User> userList = userService.findAll();
//2. 返回数据(json)
return userList;
}
}
在项目开发中,基于@Autowired进行依赖注入时,基本都是第一种和第二种方式。(官方推荐第二种方式,因为会更加规范)但是在企业项目开发中,很多的项目中,也会选择第一种方式因为更加简洁、高效(在规范性方面进行了妥协)。
注意事项
那如果在IOC容器中,存在多个相同类型的bean对象,会出现什么情况呢?
在下面的例子中,我们准备了两个UserService的实现类,并且都交给了IOC容器管理。 代码如下:

此时,我们启动项目会发现,控制台报错了:

出现错误的原因呢,是因为在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是按照名称注入












浙公网安备 33010602011771号