前言

Spring Boot 作为当前 Java 开发领域最流行的框架之一,以其 "约定优于配置" 的理念极大简化了企业级应用的开发流程。本文将基于《Spring Boot 项目开发教程(慕课版)》中的资产管理系统项目,深入解析 Spring Boot 的核心技术模块,并通过实战代码示例展示各模块的实际应用。

一、Spring Boot 核心配置

Spring Boot 的核心优势在于其自动化配置机制,通过极少的配置即可快速搭建生产级应用。以下是一个标准 Spring Boot 项目的启动类与基础配置示例:

// AssetsManagerApplication.java类
@SpringBootApplication
@EnableCaching
@EnableScheduling
public class AssetsManagerApplication {public static void main(String[] args) {SpringApplication.run(AssetsManagerApplication.class, args);}
}# application.yml
server:port: 8097tomcat:uri-encoding: UTF-8max-connections: 10000spring:datasource:url: jdbc:mysql://localhost:3306/am?useUnicode=true&characterEncoding=utf8username: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driverthymeleaf:cache: falseencoding: UTF-8

上述代码展示了 Spring Boot 项目的基本结构:启动类通过 @SpringBootApplication 注解开启自动配置,配置文件中定义了服务器端口、数据库连接等基础参数。Spring Boot 会自动扫描主类所在包及其子包中的组件,并根据类路径中的依赖自动配置相关组件。

1.自定义配置与多环境管理

在实际开发中,常需要自定义配置或针对不同环境(开发、测试、生产)进行差异化配置:

// MyConfiguration.java类
@Configuration
public class MyConfiguration {@Beanpublic PaginationInterceptor paginationInterceptor() {PaginationInterceptor paginationInterceptor = new PaginationInterceptor();paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));return paginationInterceptor;}
}# application-dev.yml
spring:profiles: devdatasource:url: jdbc:mysql://dev-server:3306/am_dev# application-prod.yml
spring:profiles: proddatasource:url: jdbc:mysql://prod-server:3306/am_prod

通过 @Configuration 注解定义配置类,使用 @Bean 注解注册自定义组件。多环境配置通过 application-{profile}.yml 文件实现,通过 spring.profiles.active 属性指定当前激活的环境。

详细讲解如下:
1. 自定义配置的核心机制

在Spring Boot中,@Configuration注解标记一个类为配置类,该类包含bean定义方法。@Bean注解用于方法上,表示该方法创建的对象将由Spring容器管理为一个bean。这允许自定义组件,如拦截器、服务或数据源。

  • 为什么使用:这避免了XML配置,使代码更简洁。例如,在您的MyConfiguration类中,paginationInterceptor()方法定义了一个分页拦截器bean,Spring在启动时会自动实例化并注入它。
  • 关键点:bean的生命周期由Spring管理,支持依赖注入(如通过构造函数或setter注入其他bean)。
2. 多环境配置的实现方式

多环境配置通过application-{profile}.yml文件实现,其中{profile}是环境标识(如devtestprod)。这可以通过spring.profiles.active属性指定当前激活的环境。

  • 激活环境:在默认的application.yml文件中设置spring.profiles.active=dev,Spring Boot会自动加载application-dev.yml中的配置。例如:
# application.yml (主配置文件)
spring:profiles:active: dev  # 指定激活开发环境
  • 优势:环境隔离配置(如数据库URL、API密钥),避免硬编码,提升安全性和可维护性。在您的例子中,application-dev.ymlapplication-prod.yml分别定义了开发和生产环境的数据库连接。
3. 具体示例扩展

下面是常见场景的示例

示例1: 自定义数据源bean(扩展@Bean用法)

假设您需要根据不同环境使用不同的数据源(如开发环境用H2内存数据库,生产环境用MySQL)。可以在配置类中定义bean,并利用环境变量注入属性。

// CustomDataSourceConfig.java类
@Configuration
public class CustomDataSourceConfig {@Value("${spring.datasource.url}")  // 从YAML注入URLprivate String url;@Beanpublic DataSource dataSource() {// 根据环境变量创建数据源HikariDataSource dataSource = new HikariDataSource();dataSource.setJdbcUrl(url);return dataSource;}
}

在YAML文件中定义环境特定属性:

# application-dev.yml
spring:profiles: devdatasource:url: jdbc:h2:mem:testdb  # 开发环境用内存数据库# application-prod.yml
spring:profiles: proddatasource:url: jdbc:mysql://prod-server:3306/am_prod  # 生产环境用真实数据库

  • 效果:当spring.profiles.active=dev时,dataSource()方法使用H2 URL;在prod环境下,自动切换到MySQL。
示例2: 多环境日志配置(扩展YAML文件)

不同环境可能需要不同的日志级别(如开发环境输出详细日志,生产环境仅记录错误)。通过YAML文件实现:

# application-dev.yml
spring:profiles: dev
logging:level:root: DEBUG  # 开发环境:详细日志# application-prod.yml
spring:profiles: prod
logging:level:root: ERROR  # 生产环境:仅错误日志
  • 无需额外代码:Spring Boot自动应用这些配置,无需修改日志框架代码。
示例3: 自定义服务bean(请自主结合自身环境属性定义配置)

假设您有一个邮件服务,在开发环境使用模拟发送,在生产环境使用真实SMTP。定义bean时注入环境相关属性:

// MailServiceConfig.java类
@Configuration
public class MailServiceConfig {@Value("${mail.enabled}")  // 从YAML注入是否启用private boolean enabled;@Value("${mail.host}")  // 注入主机地址private String host;@Beanpublic MailService mailService() {return new MailService(enabled, host);  // 创建bean,参数来自环境配置}
}

在YAML文件中设置环境特定值:

# application-dev.yml
spring:profiles: dev
mail:enabled: false  # 开发环境禁用真实发送host: localhost# application-prod.yml
spring:profiles: prod
mail:enabled: true   # 生产环境启用host: smtp.prod-server.com
  • 效果:bean行为随环境变化,提高代码灵活性,

注:通常解决环境差异导致的配置问题, 特别适合微服务架构或持续集成/持续部署(CI/CD)流程中处理环境相关配置。

二、数据库操作与持久层框架整合

Spring Boot 对主流持久层框架提供了良好的整合支持,以下是三种常见持久层方案的整合示例:

1. JdbcTemplate 基础操作

// SysRoleDaoImpl.java类
@Repository
public class SysRoleDaoImpl implements SysRoleDao {@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic int saveSys_role(Sys_role sys_role) {String sql = "INSERT INTO sys_role(name, description) VALUES(?, ?)";return jdbcTemplate.update(sql, sys_role.getName(), sys_role.getDescription());}@Overridepublic List<Sys_role> getAllSys_role() {String sql = "SELECT * FROM sys_role";return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Sys_role.class));}
}

JdbcTemplate 提供了简单的 SQL 操作封装,适合快速开发简单数据访问场景,但在复杂业务场景下 SQL 语句维护较为繁琐。

解释回答:JdbcTemplate 作为 Spring 提供的 JDBC 模板工具,确实在简单数据访问场景下表现出色,但在复杂业务场景中,由于 SQL 语句需要硬编码且缺乏 ORM 映射能力,会暴露出以下常见问题:

1.1 动态 SQL 构建复杂

当业务需要根据不同条件动态拼接 SQL 时,JdbcTemplate 需要手动处理条件拼接,容易出现 SQL 注入风险或语法错误。

// 示例:复杂条件查询用户列表(动态条件拼接)
public List<User> findUsersByConditions(String name, Integer age, String role, Boolean active) {StringBuilder sql = new StringBuilder("SELECT * FROM users WHERE 1=1");List<Object> params = new ArrayList<>();if (name != null && !name.isEmpty()) {sql.append(" AND name LIKE ?");params.add("%" + name + "%");}if (age != null) {sql.append(" AND age > ?");params.add(age);}if (role != null && !role.isEmpty()) {sql.append(" AND role = ?");params.add(role);}if (active != null) {sql.append(" AND active = ?");params.add(active);}// 可能还需要添加排序、分页等条件sql.append(" ORDER BY create_time DESC");try {return jdbcTemplate.query(sql.toString(), params.toArray(), (rs, rowNum) -> new User(rs.getInt("id"),rs.getString("name"),rs.getInt("age"),rs.getString("role"),rs.getBoolean("active")));} catch (DataAccessException e) {log.error("动态SQL查询失败: {}", sql.toString(), e);throw new BusinessException("查询失败");}
}

问题分析

  • SQL 语句通过字符串拼接实现,可读性差且容易出错
  • 条件判断逻辑与 SQL 语句耦合,维护困难
  • 结果集映射需要手动处理每一个字段,重复代码多
  • 缺乏类型安全检查,参数类型错误可能在运行时才发现

1.2. 复杂关联查询难以维护

涉及多表关联、子查询的复杂查询,SQL 语句长度和复杂度急剧增加,JdbcTemplate 难以管理。

// 示例:多表关联查询资产领用记录(资产表、部门表、用户表关联)
public List<AssetRecordVO> getAssetRecordsWithDetails() {String sql = "SELECT " +"a.id as asset_id, a.name as asset_name, a.specification, " +"d.id as dept_id, d.name as dept_name, " +"u.id as user_id, u.username, " +"r.quantity, r.apply_time, r.status " +"FROM asset a " +"JOIN asset_record r ON a.id = r.asset_id " +"JOIN department d ON r.dept_id = d.id " +"JOIN users u ON r.user_id = u.id " +"WHERE r.status = 'APPROVED' " +"ORDER BY r.apply_time DESC";try {return jdbcTemplate.query(sql, (rs, rowNum) -> {AssetRecordVO vo = new AssetRecordVO();vo.setAssetId(rs.getInt("asset_id"));vo.setAssetName(rs.getString("asset_name"));vo.setSpecification(rs.getString("specification"));vo.setDeptId(rs.getInt("dept_id"));vo.setDeptName(rs.getString("dept_name"));vo.setUserId(rs.getInt("user_id"));vo.setUsername(rs.getString("username"));vo.setQuantity(rs.getInt("quantity"));vo.setApplyTime(rs.getTimestamp("apply_time"));vo.setStatus(rs.getString("status"));return vo;});} catch (DataAccessException e) {log.error("关联查询失败: {}", sql, e);throw new BusinessException("查询资产记录失败");}
}

问题分析

  • SQL 语句长达数十行,难以阅读和调试
  • 字段别名映射容易出错(如a.id as asset_id
  • 结果集映射需要手动处理所有关联表的字段
  • 当表结构变更时,SQL 和映射代码都需要修改
  • 缺乏 SQL 重构支持,修改字段顺序或添加条件成本高

1.3. 事务管理复杂性

复杂业务可能涉及多个数据库操作的事务管理,JdbcTemplate 需要手动处理事务边界。

// 示例:资产领用与库存扣减的事务操作
@Transactional
public void processAssetIssue(Long assetId, Integer quantity, Long userId) {// 1. 查询资产库存String checkSql = "SELECT quantity FROM asset_stock WHERE asset_id = ?";Integer currentStock = jdbcTemplate.queryForObject(checkSql, Integer.class, assetId);if (currentStock < quantity) {throw new BusinessException("库存不足,无法领用");}// 2. 扣减库存String updateStockSql = "UPDATE asset_stock SET quantity = quantity - ? WHERE asset_id = ?";int updateStockResult = jdbcTemplate.update(updateStockSql, quantity, assetId);if (updateStockResult != 1) {throw new BusinessException("库存更新失败");}// 3. 创建领用记录String insertRecordSql = "INSERT INTO asset_issue (asset_id, quantity, user_id, issue_time) " +"VALUES (?, ?, ?, NOW())";int insertRecordResult = jdbcTemplate.update(insertRecordSql, assetId, quantity, userId);if (insertRecordResult != 1) {// 手动回滚需要额外代码,@Transactional在此处已失效throw new BusinessException("领用记录创建失败");}// 4. 发送通知(假设需要调用外部服务,可能抛出异常)notifyUserOfIssue(userId, assetId, quantity);
}

问题分析

  • 事务边界通过@Transactional注解实现,但复杂逻辑中难以覆盖所有异常场景
  • 手动检查更新结果(如updateStockResult != 1)增加代码复杂度
  • 跨服务调用(如notifyUserOfIssue)抛出异常时,事务回滚依赖 Spring 的默认规则
  • 缺乏声明式事务的高级控制(如事务传播行为、超时设置)
  • 多个 SQL 操作的一致性保证依赖开发者手动处理

1.4. 分页与排序处理繁琐

复杂业务中的分页查询需要手动处理 SQL 分页语法,不同数据库方言需要不同处理。

// 示例:带复杂条件的分页查询(MySQL方言)
public Page<Asset> getAssetsWithPagination(AssetQueryCriteria criteria, int page, int size) {StringBuilder sql = new StringBuilder("SELECT * FROM assets WHERE 1=1");List<Object> params = new ArrayList<>();// 构建查询条件if (criteria.getName() != null) {sql.append(" AND name LIKE ?");params.add("%" + criteria.getName() + "%");}if (criteria.getTypeId() != null) {sql.append(" AND type_id = ?");params.add(criteria.getTypeId());}if (criteria.getStatus() != null) {sql.append(" AND status = ?");params.add(criteria.getStatus());}// 计算总数String countSql = "SELECT COUNT(*) " + sql.toString();int total = jdbcTemplate.queryForObject(countSql, Integer.class, params.toArray());// 构建分页SQL(MySQL)String pageSql = sql.toString() + " ORDER BY create_time DESC LIMIT ? OFFSET ?";params.add(size);params.add((page - 1) * size);List<Asset> items = jdbcTemplate.query(pageSql, params.toArray(), (rs, rowNum) -> {Asset asset = new Asset();asset.setId(rs.getLong("id"));asset.setName(rs.getString("name"));asset.setTypeId(rs.getLong("type_id"));asset.setStatus(rs.getString("status"));asset.setPurchaseDate(rs.getDate("purchase_date"));return asset;});return Page.of(items, PageRequest.of(page - 1, size), total);
}

问题分析

  • 分页逻辑需要手动拼接LIMITOFFSET参数
  • 不同数据库(如 Oracle 的ROWNUM、SQL Server 的OFFSET-FETCH)需要不同分页语法
  • 总数查询和分页查询分离,增加代码量
  • 排序条件硬编码在 SQL 中,难以动态调整
  • 缺乏分页参数的类型安全检查

1.5. 批量操作性能问题

大量数据的批量操作(如批量插入、更新)需要手动拼接 SQL,性能优化困难。

// 示例:批量更新资产状态(1000条记录)
public void batchUpdateAssetStatus(List<Long> assetIds, String newStatus) {if (assetIds == null || assetIds.isEmpty()) {return;}// 手动拼接IN条件(注意:超过数据库IN参数限制会报错)StringBuilder sql = new StringBuilder("UPDATE assets SET status = ? WHERE id IN (");for (int i = 0; i < assetIds.size(); i++) {sql.append("?");if (i < assetIds.size() - 1) {sql.append(", ");}}sql.append(")");// 构建参数列表(第一个参数是status,后面是ids)List<Object> params = new ArrayList<>();params.add(newStatus);params.addAll(assetIds);try {jdbcTemplate.update(sql.toString(), params.toArray());} catch (DataAccessException e) {log.error("批量更新失败: {}", sql.toString(), e);throw new BusinessException("批量更新资产状态失败");}
}

问题分析

  • 批量操作通过 IN 条件实现,受数据库参数数量限制(如 MySQL 默认限制为 1000 个参数)
  • 大数量批量操作可能导致 SQL 语句过长,超出数据库协议限制
  • 缺乏批量操作的优化支持(如 JDBC 的 batch update)
  • 错误处理颗粒度粗,无法知道哪条记录更新失败
  • 性能受 SQL 解析和参数绑定影响,不如 ORM 框架的批量操作优化

1.6.总结:JdbcTemplate 的适用场景与替代方案
  • 适用场景
    • 简单 CRUD 操作(如单表查询、插入、更新)
    • 原型开发或快速验证业务逻辑
    • 对性能要求极高且 SQL 逻辑简单的场景
  • 复杂场景替代方案
    • MyBatis/MyBatis-Plus:通过 XML 或注解管理 SQL,支持动态 SQL 和结果映射
    • Spring Data JPA:基于 JPA 规范的声明式查询,适合领域模型驱动开发
    • ORM 框架(如 Hibernate):完全对象 - 关系映射,减少 SQL 编码

注:在企业级复杂业务中,建议根据业务复杂度选择合适的持久层方案,JdbcTemplate 更适合作为简单场景的辅助工具,而非核心解决方案。

2. MyBatis-Plus 高级封装

// SysDepartmentMapper.java类
@Mapper
public interface SysDepartmentMapper extends BaseMapper<SysDepartment> {List<SysDepartmentListResp> list(@Param("params") SysDepartmentListReq params,@Param("offset") Integer offset, @Param("limit") Integer limit);
}// SysDepartmentServiceImpl.java
@Service
public class SysDepartmentServiceImpl implements SysDepartmentService {@Autowiredprivate SysDepartmentMapper sysDepartmentMapper;@Overridepublic SysDepartmentVo queryList(Integer current, Integer size) {SysDepartmentVo vo = new SysDepartmentVo();IPage<SysDepartment> page = new Page<>(current, size);sysDepartmentMapper.selectPage(page, null);vo.setSys_departmentList(page.getRecords());vo.setTotal(page.getTotal());return vo;}
}

MyBatis-Plus 在 MyBatis 基础上提供了强大的 CRUD 封装和分页插件,通过继承 BaseMapper 即可获得基础操作能力,复杂查询可通过自定义 SQL 实现。

注: MyBatis-Plus 并未改变 MyBatis 的核心机制,而是通过继承扩展插件机制为其添加了更多实用功能(如: 基础 CRUD 操作自动化、物理分页插件、动态条件构造器、代码生成器、性能分析插件、逻辑删除支持),避免了重复编写基础 CRUD 代码。其设计遵循 “约定优于配置” 原则,通过命名规范和默认实现减少开发量。

附:MyBatis-Plus 与 MyBatis 的对比

3. Spring Data JPA 声明式查询

// SysAssetTypeDao.java
public interface SysAssetTypeDao extends JpaRepository<SysAssetType, Integer> {List<SysAssetType> getSysAssetTypeByIdEquals(Integer id);List<SysAssetType> getSysAssetTypeByNameStartingWith(String name);@Query("select s from sys_asset_type s")List<SysAssetType> getAllSysAssetType();
}// SysAssetTypeServiceImpl.java
@Service
public class SysAssetTypeServiceImpl implements SysAssetTypeService {@Autowiredprivate SysAssetTypeDao sysAssetTypeDao;@Overridepublic void saveSysAssetType(SysAssetType sysAssetType) {sysAssetTypeDao.save(sysAssetType);}
}

Spring Data JPA 基于 JPA 规范提供了声明式查询能力,通过方法名约定或 @Query 注解即可实现数据访问,适合领域模型清晰的场景

场景解释:

Spring Data JPA 的适用场景边界

  • 适合场景

    1. 业务领域概念明确(如资产管理系统中的资产类型、领用记录等);
    2. 实体类设计遵循业务驱动(而非数据库驱动);
    3. 查询以单表操作、简单条件查询为主。
  • 不适合场景

    1. 领域模型频繁变更或尚未明确;
    2. 存在大量复杂 SQL(如多表嵌套子查询、动态分组统计);
    3. 对 SQL 性能要求极致优化(需手动编写原生 SQL)。

注:Spring Data JPA 的核心优势在于 “以领域模型为桥梁,将对象操作映射为数据访问”,这要求领域模型必须与业务概念高度一致,否则其声明式查询的便捷性将大打折扣。

三、Web 项目开发与视图层整合

Spring Boot 对 Web 开发的支持以 Spring MVC 为基础,结合 Thymeleaf 模板引擎可快速构建前后端不分离应用:

前端:

<!-- sysPurchaseRecord.html -->
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>资产采购列表</title>
</head>
<body><table class="table"><thead><tr><th>采购人</th><th>资产名称</th><th>采购状态</th></tr></thead><tbody><tr th:each="spr, iterStat : ${sysPurchaseRecords}"><td th:text="${spr.buyerName}"></td><td th:text="${spr.assetName}"></td><td th:text="${spr.purchaseStatus == 1 ? '采购中' : '已完成'}"></td></tr></tbody></table>
</body>
</html>

后端:

// SysPurchaseRecordController.java
@RestController
@RequestMapping("/sysPurchaseRecord")
public class SysPurchaseRecordController {@Resourceprivate SysPurchaseRecordService sysPurchaseRecordService;@ApiOperation(value = "采购列表")@GetMapping("/list")public ChorResponse<Map<String, Object>> list(@ModelAttribute SysPurchaseRecordListReq req) {return ChorResponseUtils.success(sysPurchaseRecordService.list(req));}
}

Thymeleaf 模板引擎支持在 HTML 中直接嵌入数据展示逻辑,通过 th: 前缀的属性实现数据绑定和条件渲染。控制层通过 @RestController 注解返回 JSON 数据,或通过 @Controller 配合视图解析器返回页面。

附:

四、缓存与消息队列集成

1. Redis 缓存实现

// RedisCacheConfig.java类
@Configuration
public class RedisCacheConfig extends CachingConfigurerSupport {@Beanpublic RedisTemplate<String, Object> redisTemplate() {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();redisTemplate.setValueSerializer(serializer);redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setConnectionFactory(connectionFactory);return redisTemplate;}@Beanpublic CacheManager cacheManager() {RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())).disableCachingNullValues();return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).build();}
}// SysAssetServiceImpl.java类
@Service
public class SysAssetServiceImpl implements SysAssetService {@Cacheable(cacheNames = {"sysAsset"}, key = "#id")@Overridepublic SysAssetResp find(Long id) {SysAssetResp sysAssetResp = sysAssetMapper.getOne(id);// 业务逻辑处理return sysAssetResp;}
}

通过 Spring Cache 注解体系结合 Redis 实现缓存功能,@Cacheable 注解自动将方法返回值存入缓存,@CacheEvict 用于缓存失效,@CachePut 用于缓存更新。

Redis 实现 Spring Boot 缓存功能的原理示意图

1.1 扩展:Redis 缓存工作原理详解

1.1.1 @Cacheable 执行流程
// 伪代码实现逻辑
public Object cachedMethod(参数) {1. 生成缓存Key (如 "sysAsset::123")2. 调用 cacheManager.getCache("sysAsset").get(key)3. if (缓存存在) {return 反序列化(缓存值); // 直接返回}4. 执行实际方法体 (数据库查询等)5. 将结果存入缓存: cache.put(key, 序列化(结果))6. 返回结果
}
1.1.2 关键配置解析
@Bean
public CacheManager cacheManager() {RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600)) // 10分钟过期.serializeKeysWith(StringRedisSerializer.INSTANCE) // Key序列化.serializeValuesWith(Jackson2JsonRedisSerializer.INSTANCE) // Value序列化.disableCachingNullValues(); // 不缓存nullreturn RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build();
}
1.1.3.缓存注解对比

1.1.4 监控与调试:
使用 redis-cli monitor 命令观察缓存操作
启用 Spring Boot Actuator 的 cache 端点
日志配置:logging.level.org.springframework.cache=DEBUG

2. RabbitMQ 消息队列

// TopicRabbitMQConfig.java类
@Configuration
public class TopicRabbitMQConfig {@Value("${rabbitmq.queue}")private String queue;@Value("${rabbitmq.exchange}")private String exchange;@Value("${rabbitmq.routingKey}")private String routingKey;@Beanpublic TopicExchange getExchangeName() {return new TopicExchange(exchange);}@Beanpublic Queue getQueueName() {return new Queue(queue);}@Beanpublic Binding declareBinding() {return BindingBuilder.bind(getQueueName()).to(getExchangeName()).with(routingKey);}
}// TopicReceiver.java类
@Component
@RabbitListener(queues = "test02")
public class TopicReceiver {private static final Logger log = LoggerFactory.getLogger(TopicReceiver.class);@RabbitHandlerpublic void handleMessage(String message) {log.info("test02 队列接收到的消息是:{}", message);}
}

RabbitMQ 集成通过配置交换器、队列和绑定关系实现消息路由,@RabbitListener 注解标注的方法会自动监听指定队列的消息并处理。

 RabbitMQ 在 Spring Boot 中集成的原理示意图

2.1 RabbitMQ 工作原理详解

1. 核心组件关系

2.TopicExchange 路由规则

  • * (星号) 匹配一个单词

  • # (井号) 匹配零个或多个单词

routingKey        匹配模式
---------         ---------
asset.create      asset.*
asset.update      asset.*
user.notify       user.#
system.alert      *.*

3.  消息处理流程

// 生产者发送消息
@Autowired
private RabbitTemplate rabbitTemplate;public void sendMessage(String message) {// 发送到 exchange,指定 routingKeyrabbitTemplate.convertAndSend(exchange, routingKey, message);
}// 消费者处理消息
@Component
@RabbitListener(queues = "test02")
public class TopicReceiver {@RabbitHandlerpublic void handleMessage(String message) {// 处理消息逻辑log.info("接收消息: {}", message);}
}

4. 关键配置解析

@Configuration
public class TopicRabbitMQConfig {// 创建 Topic Exchange@Beanpublic TopicExchange topicExchange() {return new TopicExchange("asset-exchange");}// 创建队列@Beanpublic Queue assetQueue() {return new Queue("asset-queue");}// 绑定队列到 Exchange@Beanpublic Binding binding(Queue assetQueue, TopicExchange exchange) {// asset.* 匹配 asset.create, asset.update 等return BindingBuilder.bind(assetQueue).to(exchange).with("asset.*");}
}

5. 消息生命周期管理

6. 监控与维护:
使用 RabbitMQ Management UI(默认端口15672)
集成 Spring Boot Actuator 监控端点
关键指标监控:消息积压率、ACK延迟、重试次数

五、安全机制实现

1. JWT 认证

// JwtUtil.java类
public class JwtUtil {public static String createJwt(long ttlMillis, SysUser sysUser, String fillArgs) {SignatureAlgorithm algorithm = SignatureAlgorithm.HS256;long nowMillis = System.currentTimeMillis();Date now = new Date(nowMillis);String key = "token16546461";Map<String, Object> claims = new HashMap<>();claims.put("id", sysUser.getId());claims.put("name", sysUser.getUsername());return Jwts.builder().setClaims(claims).setId(UUID.randomUUID().toString()).setIssuedAt(now).setExpiration(new Date(nowMillis + ttlMillis)).signWith(algorithm, key).compact();}public static Claims parseJwt(String token) throws ChorBizException {String key = "token16546461";return Jwts.parser().setAllowedClockSkewSeconds(604800).setSigningKey(key).parseClaimsJws(token).getBody();}
}// ApiInterceptor.java类
@Component
public class ApiInterceptor extends HandlerInterceptorAdapter {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token = request.getHeader("Authorization");Claims claims = JwtUtil.parseJwt(token);// 验证用户存在性return true;}
}
JWT 认证流程

请求处理流程

JWT 认证通过生成包含用户信息的令牌实现无状态认证,请求时通过请求头携带令牌,拦截器验证令牌有效性。

2. Shiro 授权

// MyShiroRealm.java类
public class MyShiroRealm extends AuthorizingRealm {@Resourceprivate SysUserMapper sysUserMapper;@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {SysUser sysUser = (SysUser) principalCollection.getPrimaryPrincipal();SimpleAuthorizationInfo authorization = new SimpleAuthorizationInfo();List<SysRole> roleInfoList = sysRoleMapper.getRoleList(sysUser.getId());if (roleInfoList != null && !roleInfoList.isEmpty()) {roleInfoList.forEach(role -> authorization.addRole(role.getCode()));List<SysPermission> permissionInfoList = sysPermissionMapper.getPermissionList(role.getId());if (permissionInfoList != null && !permissionInfoList.isEmpty()) {List<String> permissions = permissionInfoList.stream().map(SysPermission::getPermission).collect(Collectors.toList());authorization.addStringPermissions(permissions);}}return authorization;}
}// SysUnitController.java类
@RestController
@RequestMapping("/sysUnit")
public class SysUnitController {@RequiresRoles(value = {"admin"})@PostMapping("/create")public ChorResponse<Void> create(@RequestBody SysUnitReq req) {sysUnitServiceImpl.save(req);return ChorResponseUtils.success();}@RequiresPermissions(value = {"unit:delete"})@DeleteMapping("/{id}")public ChorResponse<Void> remove(@PathVariable Long id) {sysUnitServiceImpl.remove(id);return ChorResponseUtils.success();}
}



JWT 认证与 Shiro 授权的整合原理图

Shiro 通过 Realm 实现授权信息加载,@RequiresRoles 和 @RequiresPermissions 注解实现方法级别的权限控制。

六、任务管理与异步处理

1. 定时任务

// ScheduleTimer.java类
@Component
public class ScheduleTimer {private Logger logger = LoggerFactory.getLogger(this.getClass());@Resourceprivate SysReceiveRecordMapper sysReceiveRecordMapper;// 每6小时执行一次@Scheduled(cron = "0 0 0/6 * * ?")public void executeUpdateCuTask() {Thread current = Thread.currentThread();logger.info("定时任务线程:{}", current.getName());List<SysReceiveRecord> records = sysReceiveRecordMapper.selectList(new QueryWrapper<SysReceiveRecord>().eq("status", ParamsConstant.RECEIVE_STATUS_RECEIVE));long currentTime = System.currentTimeMillis();for (SysReceiveRecord record : records) {long useTime = currentTime - record.getUpdateTime();int hours = (int) (useTime / 1000 / 3600);logger.info("资产领用时间已达:{}小时", hours);// 发送提醒通知}}
}

通过 @Scheduled 注解实现定时任务,cron 表达式支持复杂时间规则定义,适用于周期性数据处理场景。

2. 异步任务与邮件服务

// AsyncService.java类
@Service
public class AsyncService {@Autowiredprivate JavaMailSender mailSender;@Asyncpublic void sendEmail(String to, String subject, String content) {SimpleMailMessage message = new SimpleMailMessage();message.setTo(to);message.setSubject(subject);message.setText(content);mailSender.send(message);}
}// AssetsReturnService.java类
@Service
public class AssetsReturnService {@Autowiredprivate AsyncService asyncService;public void remindOverdueReturn(Long userId, String assetName) {SysUser user = sysUserMapper.selectById(userId);asyncService.sendEmail(user.getEmail(), "资产归还提醒", "尊敬的" + user.getUsername() + ",您领用的" + assetName + "已超过归还期限,请及时处理。");}
}

@Async 注解实现异步方法调用,邮件服务通过 JavaMailSender 接口实现,适用于耗时操作如发送邮件、生成报表等场景。

异步任务与邮件服务的原理示意图

异步邮件发送完整流程

七、项目部署与最佳实践

1. 项目打包与部署

<!-- pom.xml 打包配置 -->
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><mainClass>com.cg.test.am.AssetsManagerApplication</mainClass><settings><url>http://maven.aliyun.com/nexus/content/groups/public/</url></settings></configuration></plugin></plugins>
</build>

通过 Maven 插件打包可执行 JAR 包,部署命令:java -jar assets-manager-1.0.0.jar --spring.profiles.active=prod

2. 企业级应用最佳实践

  • 分层架构:严格遵循 Controller-Service-Dao 分层,各层职责清晰
  • 接口规范:采用 RESTful 接口设计,统一响应格式
  • 异常处理:全局异常处理器统一处理业务异常
  • 日志规范:使用统一日志格式,区分业务日志和系统日志
  • 监控告警:集成 Actuator 监控端点,配置健康检查和告警机制

结语

Spring Boot 通过自动化配置和开箱即用的组件集成,极大降低了企业级应用的开发门槛。本文通过资产管理系统项目实例,全面展示了 Spring Boot 从基础配置到高级特性的完整应用链条。在实际开发中,开发者应根据项目规模和业务复杂度,灵活选择合适的技术方案,同时遵循最佳实践,构建可维护、可扩展的高质量应用系统。

随着微服务架构的普及,Spring Boot 与 Spring Cloud 的结合将成为企业级应用开发的主流方向,后续可进一步探索服务注册与发现、配置中心、链路追踪等高级主题,实现更复杂的分布式系统架构。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/88200.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/88200.shtml
英文地址,请注明出处:http://en.pswp.cn/web/88200.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ByteBrain x 清华 VLDB25|时序多模态大语言模型 ChatTS

资料来源&#xff1a;火山引擎-开发者社区 近年来&#xff0c;多模态大语言模型&#xff08;MLLM&#xff09;发展迅速&#xff0c;并在图像、视频、音频等领域取得了突破性成果。然而&#xff0c;相较于这些研究较为成熟的模态&#xff0c;时间序列这一类型的数据与大模型结合…

WPF学习笔记(25)MVVM框架与项目实例

MVVM框架与项目实例一、MVVM框架1. 概述2. 核心组件与优势一、MVVM项目1.普通项目2. MVVM架构3. MVVM项目实例1. 项目准备2. LoginViewModel与Login2. MainWindowViewModel4. MVVM项目优化1. BaseViewModel2. RealyCommand3. 效果展示总结一、MVVM框架 1. 概述 官方文档&…

MySQL实操

## 基于MySQL#先启动MySQL服务#第一次登录[rootlocalhost ~]# mysql -uroot -P3306#密码登录[rootlocalhost ~]# mysql -uroot -pEnter password: Welcome to the MySQL monitor. Commands end with ; or \g.Your MySQL connection id is 9Server version: 8.0.41 Source dist…

ez_rust_writeup

一道简单的[[rust逆向]] #rust逆向 #位运算 题目信息 文件名&#xff1a;ezrust.exe 题目附件&#xff1a;https://wwfj.lanzoul.com/iczMR30k5j4h 密码:bueq 题目分析 1. 初步分析 这是一道Rust编写的逆向题目。通过IDA分析可以看到&#xff0c;这是一个典型的flag验证程序。 …

【QT】-隐式转换 explicit用法

通俗易懂的解释:隐式转换 vs 显式转换 什么是隐式转换? 隐式转换就是编译器偷偷帮你做的类型转换,你甚至都没意识到它发生了。 例子: cpp 运行 double x = 5; // 隐式:int → double(5 变成 5.0) int y = x * 2.5; // 隐式:double → int(截断小数部分) 构造函数的隐…

Django核心知识点详解:JSON、AJAX、Cookie、Session与用户认证

1. JSON数据格式详解1.1 什么是JSON&#xff1f;JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;具有以下特点&#xff1a;独立于语言&#xff0c;几乎所有编程语言都支持易于人阅读和编写易于机器解析和生成基于文本&#xff…

[特殊字符] Python 实战 | 批量统计中文文档词频并导出 Excel

本文展示如何用 Python 脚本&#xff1a; 批量读取文件夹中的多篇中文文档&#xff1b; 用 jieba 分词并统计词频&#xff08;过滤停用词与单字符&#xff09;&#xff1b; 将各文档词频输出为对应 Excel 文件&#xff1b; 是文本分析、内容审查、报告编写中的实用技巧。 &…

共享打印机(详细操作+常见问题:需输入用户名密码、无法连接等)

文章目录一、设置打印机共享的准备工作二、Windows系统下打印机共享设置1. 启用主机打印机共享2. 客户端添加共享打印机三、我所遇到的问题及解决方法客户机遇到输入用户名、密码错误代码 0x0000011b一、错误代码 0x0000011b 的含义二、解决方法添加打印机没成功其他问题此次打…

在 Windows 系统上配置 [go-zero](https://go-zero.dev) 开发环境教程

&#x1f4bb; 在 Windows 系统上配置 go-zero 开发环境教程 本教程将详细介绍如何在 Windows 系统上配置 go-zero 微服务框架的开发环境&#xff0c;包括依赖安装、路径配置、常见问题等。 &#x1f9f1; 一、前置环境安装 1. 安装 Go 下载地址&#xff1a;https://go.dev/…

开源=白嫖?

国内有一个非常浓重的思想&#xff0c;开源&#xff0c;开源就是免费&#xff0c;就是白嫖&#xff0c;就是不花钱&#xff0c;白给。那么什么是开源&#xff1f;“源代码”是软件中大多数计算机用户从未见过的部分;它是计算机程序员可以操纵的代码&#xff0c;以改变一个软件(…

2048-控制台版本

2048控制台版 文章目录2048控制台版实现效果&#xff1a;在这里插入图片描述库函数使用&#xff1a;初始化变量功能函数实现&#xff1a;状态判断函数int Judge&#xff08;&#xff09;&#xff1b;数字生成函数 bool CtreateNumber&#xff08;&#xff09;打印游戏界面 void…

提取出Wallpaper Engine壁纸的mpkg类静态壁纸

github 地址 https://github.com/notscuffed/repkg先下载软件2853…26目录这样获取有的直接mp4格式&#xff0c;就不能用这方法准备好后 cmd 进入repkg目录 执行 repkg extract ./294...333/scene.pkg

AI健康小屋“15分钟服务圈”:如何重构社区健康生态?

AI健康小屋作为“15分钟服务圈”的核心载体&#xff0c;通过技术赋能与场景重构&#xff0c;正推动社区健康生态从被动治疗向主动预防、从单一服务向全周期管理转型。那我们应该如何重构社区健康生态呢&#xff1f;服务模式创新1.全时段覆盖AI健康小屋通过分时段服务满足不同群…

[netty5: WebSocketFrame]-源码分析

WebSocketFrame WebSocketFrame 是 Netty 中用于表示 WebSocket 消息帧的抽象基类&#xff0c;封装了帧的内容、分片标志和扩展位信息&#xff0c;供各类具体帧&#xff08;如文本、二进制、控制帧&#xff09;继承使用。 public abstract class WebSocketFrame extends Buffer…

【加解密与C】非对称加解密(三)ECC椭圆曲线

ECC椭圆曲线的基本概念椭圆曲线密码学&#xff08;Elliptic Curve Cryptography&#xff0c;ECC&#xff09;是一种基于椭圆曲线数学的公钥密码体制。与传统的RSA相比&#xff0c;ECC在相同安全级别下使用更短的密钥&#xff0c;计算效率更高&#xff0c;适用于资源受限的环境。…

力扣网编程150题:加油站(贪心解法)

一. 简介 前面一篇文章使用暴力解法来解决力扣网150 题目&#xff1a;加油站。文章如下&#xff1a; 力扣网编程150题&#xff1a;加油站&#xff08;暴力解法&#xff09;-CSDN博客 暴力解法就是遍历了所有元素作为起始点的可能&#xff0c;算法时间复杂度为 O(n*n)&#x…

windwos 设置redis长久密码不生效

1、设置长久密码redis.windows.conf 文件修改对应的设置密码2、启动时设置对应的加载配置文件

物联网(IoT)领域存在多种协议

物联网&#xff08;IoT&#xff09;领域存在多种协议&#xff0c;主要是因为不同的应用场景对通信的需求差异很大&#xff0c;包括实时性、带宽、功耗、设备兼容性、安全性等。以下从协议多样性的原因和你提到的具体协议&#xff08;如 dc3-driver-* 模块&#xff09;展开说明&…

二、encoders

文章目录一、batch_encoder (用于 BFV)1. 概述2. 数学原理3. 使用方法4. 代码示例二、ckks_encoder (用于 CKKS)在 1. bfv_basics.cpp 中&#xff0c;我们展示了如何使用BFV方案执行非常简单的计算。计算是在 plain_modulus 参数的模下执行的&#xff0c;并且 只使用了 BFV 明文…

数据一致性解决方案总结

数据一致性解决方案总结 我们在系统中&#xff0c;主要进行了数据冗余&#xff0c;那么就会带来数据一致性的问题。常见的数据一致性问题有&#xff1a;数据库主从同步延迟导致的读数据不一致&#xff1b;数据库主主之间数据的不一致&#xff1b;缓存和数据库之间的数据不一致。…