一、关于Spring
Spring的官网(https://spring.io)
Spring提供了很多开源项目,通过Spring Boot可以帮我们快速的构建应用程序。
Spring主要是为我们简化配置,快速开发。
二、Spring入门项目
需求:基于SpringBoot的方式开发一个web应用,浏览器发起请求/hello后,给浏览器返回字符串 “Hello Spring xxxx”

在左侧选择Spring Boot生成源,右侧会为我们自动生成相应配置,我们需要注意的是:
1.IDEA自动为我们配置了Spring的服务器地址(若是网络不好,可用阿里云提供的脚手架:https://start.aliyun.com)
2.开发语言我们选择Java
3.构建工具要选择Maven(项目中要导入许多相关jar包作为依赖)
4.JDK版本必须是17及以上
5.打包方式设置为Jar即可

我们是面向网页做项目,所以最基础的依赖要勾选Spring Web
Spring Boot版本号要选择正式版

在com.itheima包下创建一个HelloSpring类

运行自动创建的引导类(标识有@SpringBootApplication注解的类)
打开浏览器,输入 http://localhost:8080/hello?name=张三

三、SpringBootWeb案例1
需求:基于SpringBoot开发web程序,完成用户列表的渲染展示
准备工作:将user.txt文档以及网页前端内容都放在resources目录下

在pom.xml文件中导入lombok依赖(初始化项目时不能勾选,否则会出现编译异常)
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
首先在新建的项目包下新建一个pojo包,专门存放实体类,在pojo下新建一个User类,类中属性要与user.txt中属性名相同,避免后期对接属性产生不必要的麻烦。
package org.itheima.pojo;
import java.time.LocalDateTime;
/**
* @Author: Jaymr
* @Date: 2025/5/2 12:54
* @Version: v1.0.0
* @Description: 封装用户信息
**/
@Data//getter setter等方法
@AllArgsConstructor//有参构造
@NoArgsConstructor//无参构造
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>
在项目包下创建一个子包controller,在其中创建一个UserController类
package org.itheima.controller;
import cn.hutool.core.io.IoUtil;
import org.itheima.pojo.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.FileInputStream;
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;
/**
* @Author: Jaymr
* @Date: 2025/5/2 13:08
* @Version: v1.0.0
* @Description: 完成用户列表的渲染展示
**/
@RestController//标识为请求处理类
public class UserController {
@RequestMapping("/list")
public List<User> list(){
//1.加载并读取数据 获取用户信息
//IO流获取文件信息
// InputStream in = new FileInputStream(new File("user.txt"));
InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
//定义一个User类型的ArrayList存放用户数据
ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
//2.解析数据,封装成对象 ->集合
//定义一个User属性的List集合 userList
//stream() 将lines对集合转化为流对象
//map() 方法将lines集合中的每个元素应用一个函数,然后将结果映射为User对象
List<User> userList = lines.stream().map(line -> {
String[] parts = line.split(",");//按照,拆分字符串 拆分的字符串存放到part数组内
Integer id = Integer.parseInt(parts[0]);//Integer.parseInt() 方法将字符串转换为整数
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());
// collect() 将流处理后的结果收集起来
// Collectors.toList() 告诉程序把所有处理后的元素放进userList中
//3.响应数据
/**
* 底层会直接将list集合转换为json格式(@RestController注解底层用到@ResponseBody注解
* 将Controller返回值直接作为响应体的数据直接响应;
* 返回值是对象/集合,则将其转为json再作为响应数据返回)
*/
return userList;
}
}
启动服务测试,在浏览器中访问:http://localhost:8080/user.html

四、分层解耦
为了解决后期代码维护时的便捷性,以及提高代码的可复用性,增强代码的可读性,我们可以把一个项目分为三层架构(单一职责原则)。
以以上项目为例,大体可分为三个部分:
-
数据访问:负责业务数据的维护操作,包括增、删、改、查等操作。(Dao)
-
逻辑处理:负责业务逻辑处理的代码。(Service)
-
请求处理、响应数据:负责,接收页面的请求,给页面响应数据。(Controller)
所以我们对上述案例以三层架构的模式进行优化:
在项目包下创建三个包:dao,service,controller
dao包下新建一个接口UserDao以规范Dao层代码
package org.itheima.dao;
import org.itheima.pojo.User;
import java.util.List;
/**
* @Author: Jaymr
* @Date: 2025/5/2 13:56
* @Version: v1.0.0
**/
public interface UserDao {
public List<User> findAll();
}
在dao包下新建一个包Impl,里面存放实现类,在Impl包里新建UserDaoImpl类实现UserDao接口:
package org.itheima.dao.Impl;
import cn.hutool.core.io.IoUtil;
import org.itheima.dao.UserDao;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: Jaymr
* @Date: 2025/5/2 13:59
* @Version: v1.0.0
* @Description: TODO
**/
public class UserDaoImpl implements UserDao {
@Override
public List<String> findAll() {
//1.加载并读取数据 获取用户信息
//IO流获取文件信息
// InputStream in = new FileInputStream(new File("user.txt"));
InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
//定义一个User类型的ArrayList存放用户数据
ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
return lines;//ArrayList 是 List 接口的一个实现类 多态
}
}
在service包下进行同样的操作:
创建UserService接口:
package org.itheima.service;
import org.itheima.pojo.User;
import java.util.List;
/**
* @Author: Jaymr
* @Date: 2025/5/2 14:06
* @Version: v1.0.0
**/
public interface UserService {
public List<User> findall();
}
创建在service包下新建一个包Impl,里面存放实现类,在Impl包里新建UserServiceImpl类实现UserService接口:
package org.itheima.service.Impl;
import org.itheima.dao.Impl.UserDaoImpl;
import org.itheima.dao.UserDao;
import org.itheima.pojo.User;
import org.itheima.service.UserService;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author: Jaymr
* @Date: 2025/5/2 14:08
* @Version: v1.0.0
* @Description: TODO
**/
public class UserServiceImpl implements UserService {
//创建userDao对象
private UserDao userDao = new UserDaoImpl();
@Override
public List<User> findall() {
//1.调用Dao获取数据
List<String> lines = userDao.findAll();
//2.解析数据,封装成对象 ->集合
//定义一个User属性的List集合 userList
//stream() 将lines对集合转化为流对象
//map() 方法将lines集合中的每个元素应用一个函数,然后将结果映射为User对象
List<User> userList = lines.stream().map(line -> {
String[] parts = line.split(",");//按照,拆分字符串 拆分的字符串存放到part数组内
Integer id = Integer.parseInt(parts[0]);//Integer.parseInt() 方法将字符串转换为整数
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());
// collect() 将流处理后的结果收集起来
// Collectors.toList() 告诉程序把所有处理后的元素放进userList中
return userList;
}
}
controller包中,仅需一个UserController类,因为直接与服务器对接,无需创建接口。
package org.itheima.controller;
import cn.hutool.core.io.IoUtil;
import org.itheima.pojo.User;
import org.itheima.service.Impl.UserServiceImpl;
import org.itheima.service.UserService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.FileInputStream;
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;
/**
* @Author: Jaymr
* @Date: 2025/5/2 13:08
* @Version: v1.0.0
* @Description: TODO
**/
@RestController//标识为请求处理类
public class UserController {
private UserService userService = new UserServiceImpl();
@RequestMapping("/list")
public List<User> list(){
List<User> userList = userService.findall();
//3.响应数据
/**
* 底层会直接将list集合转换为json格式(@RestController注解底层用到@ResponseBody注解
* 将Controller返回值直接作为响应体的数据直接响应;
* 返回值是对象/集合,则将其转为json再作为响应数据返回)
*/
return userList;
}
}
启动服务测试,在浏览器中访问:http://localhost:8080/user.html,结果与原案例相同。
进一步解耦
我们注意到,每次需要一个对象,我们都要new一个相应的对象。如果我们日后要更换实现类,就需要修改调用原实现类类中的代码。(如:我们现在弃用了UserDaoImpl,使用UserDaoImpl2,那么就需要在UserServiceImpl中修改new的对象)。
解耦思路:
1.首先就不能new一个具体的对象。但是不new对象,就意味着没有上一层对象。
那么我们可以提供一个容器,容器中存储相应对象(如:UserDaoImpl,UserDaoImpl2)
然后UserServiceImpl直接从容器中获取对应对象
这里,Spring提供了两个核心概念:(专有名词和解释不看也罢,看我的解释)
-
控制反转: Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。-
对象的创建权由程序员主动创建转移到容器(由容器创建、管理对象)。这个容器称为:IOC容器或Spring容器。
-
-
依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。-
程序运行时需要某个资源,此时容器就为其提供这个资源。 -
例:EmpController程序运行时需要EmpService对象,Spring容器就为其提供并注入EmpService对象。
-
-
bean对象:IOC容器中创建、管理的对象,称之为:bean对象。
简单来说,就是你想喝水,但是你杯子里没有水,你只想用自己的杯子喝水。那就需要别人把水倒在你的杯子里,你去用自己的杯子喝水,你并不关心水是哪来的,只要有水喝就就行。
用自己的杯子喝水 → 控制反转(IOC)
别人把水倒进杯子 → 依赖注入( DI )
我自己的杯子就是DI,那个容器就是IOC
IOC/DI的核心是——通过反转控制权,将依赖关系的管理从组件内部转移到外部容器
那我们怎么实现呢?
IOC/DI实现
1.将Service以及Dao层的实现类,交给IOC容器管理
在实现类上加@Component,就代表把当前类的对象交给IOC
如: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;
}
}
2.为Controller 及 Service注入运行时所依赖的对象
使用@Autowired注解
如: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;
}
}
启动测试服务,在服务器中访问:http://localhost:8080/user.html,效果同上。
五、IOC详解
之前把对象交给IOC容器时,我们用到@Component注解,但具体是哪一层我们看不出,所以Spring给@Component加了几个衍生注解:
| 注解 | 说明 | 位置 |
| @Component | 声明bean的基础注解 | 不属于以下三类时,用此注解 |
| @Controller | @Component的衍生注解 | 标注在控制层类上 |
| @Service | @Component的衍生注解 | 标注在业务层类上 |
| @Repository | @Component的衍生注解 | 标注在数据访问层类上(由于与mybatis整合,用的少) |
六、DI详解
在入门程序案例中,我们使用了@Autowired这个注解,完成了依赖注入的操作,而这个Autowired翻译过来叫:自动装配。
@Autowired注解,默认是按照类型进行自动装配的(去IOC容器中找某个类型的对象,然后完成注入操作)
1.构造器注入(推荐):通过在类的构造方法上使用@Autowired注解,Spring会自动将匹配的Bean注入到构造器的参数中,从而实现Bean的自动装配。
@Component
public class UserService {
private UserDao userDao;
@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
2.属性注入(代码简洁,但原理是利用暴力反射,破坏代码的封装性):通过在类的属性上使用@Autowired注解,Spring会自动将匹配的Bean注入到属性中,从而实现Bean的自动装配。
@Component
public class UserService {
@Autowired
private UserDao userDao;
}
3.方法注入:通过在类的方法上使用@Autowired注解,Spring会自动将匹配的Bean注入到方法的参数中,从而实现Bean的自动装配。
@Component
public class UserService {
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
注意事项:
如果同时准备了两个UserService的实现类并且都交给了IOC容器管理,通常情况下会导致Spring无法确定要注入哪个实现类,从而出现冲突。
为了解决这个问题,可以使用@Primary、@Qualifier和@Resource三种注解来指定要注入的实现类。
1.Primary注解:可以标注在一个实现类上,告诉Spring优先选择注入这个实现类
@Component
@Primary
public class UserServiceImpl1 implements UserService {
// 实现类1的代码
}
@Component
public class UserServiceImpl2 implements UserService {
// 实现类2的代码
}
2.Qualifier注解:可以和@Autowired注解一起使用,指定具体要注入的实现类
@Autowired
@Qualifier("serviceImpl1")
private UserService userService;
@Component("serviceImpl1")
public class UserServiceImpl1 implements UserService {
// 实现类1的代码
}
@Component("serviceImpl2")
public class UserServiceImpl2 implements UserService {
// 实现类2的代码
}
3.@Resource注解:可以通过指定的name属性来指定要注入的实现类
@Resource(name = "serviceImpl1")
private UserService userService;
@Component("serviceImpl1")
public class UserServiceImpl1 implements UserService {
// 实现类1的代码
}
@Component("serviceImpl2")
public class UserServiceImpl2 implements UserService {
// 实现类2的代码
}
通过以上三种方式,可以解决同一接口有多个实现类时的冲突问题,并指定Spring注入的实现类。

1225

被折叠的 条评论
为什么被折叠?



