Lombok作用
// 不使用Lombok - 需要大量样板代码
public class User {
private Long id;
private String name;
private Integer age;
// 需要手动编写所有getter/setter
// 需要手动编写toString()
// 需要手动编写equals()和hashCode()
// 需要手动编写构造函数
}
// 使用Lombok - 代码极其简洁
@Data // 自动生成getter/setter/toString/equals/hashCode
@AllArgsConstructor // 全参数构造函数
@NoArgsConstructor // 无参数构造函数
@Builder // 建造者模式
public class User {
private Long id;
private String name;
private Integer age;
@NonNull // 非空检查
private String email;
}
Hutool的作用
//IoUtil.readLines():一行代码读取文件所有行到List中
public List<String> findAll() {
// 使用Hutool的IoUtil.readLines方法
InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
ArrayList<String> lines = IoUtil.readLines(in, "utf-8", new ArrayList<>());
return lines;
}
// 如果不使用Hutool,需要手动编写:
public List<String> findAll() {
InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
List<String> lines = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8"))) {
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
} catch (IOException e) {
throw new RuntimeException("读取文件失败", e);
}
return lines;
}
@ResponseBody
- 类型:方法注解、类注解
- 位置:书写在Controller方法上或类上
- 作用:将方法返回值直接响应给浏览器,如果返回值类型是实体对象/集合,将会转换为JSON格式后在响应给浏览器
为什么Controller中,只在类上添加了@RestController注解、方法添加了@RequestMapping注解,并没有使用@ResponseBody注解,却能给浏览器响应?
这是因为,类上加了@RestController注解,而这个注解是由两个注解组合起来的,分别是:@Controller 、@ResponseBody。 那也就意味着,类上已经添加了@ResponseBody注解了,而一旦在类上加了@ResponseBody注解,就相当于该类所有的方法中都已经添加了@ResponseBody注解。
分层解耦
高内聚
准确解释:一个模块内部的各个元素(代码、方法、类)功能相关性的程度。高内聚意味着:
- 模块内的元素共同完成一个单一、明确的功能
- 模块职责清晰,不做不相关的事情
- 修改模块时,影响范围仅限于模块内部
// ❌ 低内聚的类 - 职责混杂
public class UserManager {
// 用户管理相关
public void addUser() {...}
public void deleteUser() {...}
// 邮件发送相关(不相关功能)
public void sendEmail() {...}
// 文件操作相关(不相关功能)
public void uploadFile() {...}
}
// ✅ 高内聚的类 - 单一职责
public class UserService {
// 只处理用户相关业务
public void register() {...}
public void login() {...}
public void updateProfile() {...}
public void resetPassword() {...}
}
public class EmailService {
// 专门处理邮件相关
public void sendEmail() {...}
}
public class FileService {
// 专门处理文件相关
public void uploadFile() {...}
}
低耦合
准确解释:模块之间依赖关系的强弱程度。低耦合意味着:
- 模块之间依赖接口而不是具体实现
- 修改一个模块时,对其他模块影响最小
- 模块可以独立开发、测试和维护
//问题代码:高耦合
public class OrderService {
// 问题1:直接创建具体对象(强绑定)
private MySQLOrderDao orderDao = new MySQLOrderDao(); // 只能MySQL
private EmailSender emailSender = new EmailSender(); // 只能发邮件
public void createOrder() {
orderDao.save(order); // 问题2:如果换数据库要改代码
emailSender.send(); // 问题3:如果改发短信要改代码
}
}
- OrderService 硬编码 依赖MySQL和Email
- 想换成Redis数据库?❌ 必须改OrderService代码
- 想发短信通知?❌ 必须改OrderService代码
- 想单元测试?❌ 很难(因为依赖具体实现)
//解决方案:低耦合
// 📦 第一步:定义"标准协议"(接口)
// 就像外卖平台规定:所有饭店必须能"接单"和"送餐"
interface OrderDao {
void save(Order order); // 所有数据存储都必须实现这个方法
}
interface NotificationService {
void notifyUser(); // 所有通知服务都必须实现这个方法
}
// 🏪 第二步:不同的"商家"(实现类)
// 比如:有专门用MySQL的店
class MySQLOrderDao implements OrderDao {
@Override
public void save(Order order) {
System.out.println("使用MySQL保存订单");
}
}
// 也有专门用Redis的店
class RedisOrderDao implements OrderDao {
@Override
public void save(Order order) {
System.out.println("使用Redis保存订单");
}
}
// 有发邮件的店
class EmailNotification implements NotificationService {
@Override
public void notifyUser() {
System.out.println("发送邮件通知");
}
}
// 有发短信的店
class SMSNotification implements NotificationService {
@Override
public void notifyUser() {
System.out.println("发送短信通知");
}
}
// 🛒 第三步:顾客(OrderService)只跟平台打交道
public class OrderService {
// 只依赖"接口",不依赖具体实现
private OrderDao orderDao; // 可以接受任何实现了OrderDao的类
private NotificationService notificationService; // 可以接受任何通知服务
// 构造器注入:使用什么实现,由外部决定
public OrderService(OrderDao dao, NotificationService service) {
this.orderDao = dao;
this.notificationService = service;
}
public void createOrder() {
// 只管调用标准接口,不管具体是谁实现的
orderDao.save(order);
notificationService.notifyUser();
}
}
三层架构
- Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。
- Service:业务逻辑层。处理具体的业务逻辑。
- Dao:数据访问层(Data Access Object),也称为持久层。负责数据访问操作,包括数据的增、删、改、查。
分层

其中规范是:
分为三个包,controller、dao、service;
包下存放接口和impl子包;
子包下存放对应实现类,类名为接口名+Impl。
解耦
首先不能在UserController中使用new对象
@RestController
public class UserController {
UserService userService;//此处不new
@RequestMapping("/list")
public List<User> list(){
List<User> userList = userService.findAll();
return userList;
}
}
将要用到的对象交给一个容器管理,应用程序中用到这个对象,就直接从容器中获取
我们想要实现上述解耦操作,就涉及到Spring中的两个核心概念:
- 控制反转: Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。
- 对象的创建权由程序员主动创建转移到容器(由容器创建、管理对象)。这个容器称为:IOC容器或Spring容器。
- 依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。
- 程序运行时需要某个资源,此时容器就为其提供这个资源。
- 例:EmpController程序运行时需要EmpService对象,Spring容器就为其提供并注入EmpService对象。
- bean对象:IOC容器中创建、管理的对象,称之为:bean对象。
IOC&DI入门
将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;
}
}
为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;
}
}
IOC详解
Bean的声明
| 注解 | 说明 | 位置 |
| @Component | 声明bean的基础注解 | 不属于以下三类时,用此注解 |
| @Controller | @Component的衍生注解 | 标注在控制层类上 |
| @Service | @Component的衍生注解 | 标注在业务层类上 |
| @Repository | @Component的衍生注解 | 标注在数据访问层类上(由于与mybatis整合,用的少) |
注意1:声明bean的时候,可以通过注解的value属性指定bean的名字,如果没有指定,默认为类名首字母小写。
注意2:使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller。
组件扫描
- 前面声明bean的四大注解,要想生效,还需要被组件扫描注解
@ComponentScan扫描。 - 该注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解
@SpringBootApplication中,默认扫描的范围是启动类所在包及其子包。
DI详解
1.@Autowired
@Autowired注解,默认是按照类型进行自动装配的(去IOC容器中找某个类型的对象,然后完成注入操作)
@RestController
public class UserController {
//方式一: 属性注入
@Autowired
private UserService userService;
}
}
- 优点:代码简洁、方便快速开发。
- 缺点:隐藏了类之间的依赖关系、可能会破坏类的封装性。
@RestController
public class UserController {
//方式二: 构造器注入
private final UserService userService;
@Autowired //如果当前类中只存在一个构造函数, @Autowired可以省略
public UserController(UserService userService) {
this.userService = userService;
}
}
- 优点:能清晰地看到类的依赖关系、提高了代码的安全性。
- 缺点:代码繁琐、如果构造参数过多,可能会导致构造函数臃肿。
- 注意:如果只有一个构造函数,@Autowired注解可以省略。(通常来说,也只有一个构造函数)
/** * 用户信息Controller */
@RestController
public class UserController {
//方式三: setter注入
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
- 优点:保持了类的封装性,依赖关系更清晰。
- 缺点:需要额外编写setter方法,增加了代码量。
在项目开发中,基于@Autowired进行依赖注入时,基本都是第一种和第二种方式。(官方推荐第二种方式,因为会更加规范)但是在企业项目开发中,很多的项目中,也会选择第一种方式因为更加简洁、高效(在规范性方面进行了妥协)。
2.多个相同bean
在IOC容器中,存在多个相同类型的bean对象使用:
方案一:使用@Primary注解
当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现。
@Primary
@Service
public class UserServiceImpl implements UserService {
}
方案二:使用@Qualifier注解
指定当前要注入的bean对象。 在@Qualifier的value属性中,指定注入的bean的名称,如@Service("userService")。 @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;
}
3.面试题:@Autowird 与 @Resource的区别
- @Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解
- @Autowired 默认是按照类型注入,而@Resource是按照名称注入
Comments NOTHING