编辑
2025-08-11
JAVA-Springboot
00

Spring状态机让Java状态流转一目了然

eaab811ebb76929897d39dcd822853e9.png

状态机,就像现实中的交通信号灯控制器,绿灯(通行)→ 黄灯(过渡)→ 红灯(停止), 这种状态变化的规则就是状态机的典型应用。

Spring 状态机 是基于 Spring 框架的状态机实现,通过 spring-statemachine-core 组件(2.5.0 + 版本),能够帮助我们:

• 规范定义程序状态集合 :如订单的待支付、已支付、配送中、已完成等状态。

• 精准管理状态转换规则 :例如支付事件触发待支付 → 已支付的转换。

• 高效处理状态转换时的业务逻辑 :在状态变化过程中执行相应的操作。

当你的程序面临以下场景时,Spring 状态机将是一个绝佳的选择:

• 业务流程复杂 :涉及多个状态和事件交互,传统 if-else 实现方式难以维护。

• 状态转换规则频繁变化 :需要灵活调整状态转换逻辑,避免代码大量修改。

• 需要对状态进行集中管控 :以便统一监控和管理业务流程的状态变化。

传统 if-else 实现方式存在诸多痛点,例如:

if(currentState == "待支付"){ if(event == "支付"){ if(金额校验通过){ currentState = "已支付"; } } } else if(currentState == "已支付"){ }

这种代码结构随着业务复杂度增加,会变得难以维护和扩展。

Spring 状态机的引入与配置

在项目中引入 Spring 状态机的依赖,添加以下代码至 pom.xml 文件:

<dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-starter</artifactId> <version>3.2.0</version> </dependency>

注解 :

1.该依赖引入了 Spring 状态机的核心功能,为项目提供了状态机的实现和相关 API。

2.版本选择应根据项目实际需求和兼容性进行确定,3.2.0 版本具有良好的稳定性和功能支持。

接下来,定义状态和事件:

public enum OrderStates { UNPAID("待支付"), PAID("已支付"), DELIVERING("配送中"), COMPLETED("已完成"); private final String stateDesc; OrderStates(String stateDesc) { this.stateDesc = stateDesc; } public String getStateDesc() { return stateDesc; } } public enum OrderEvents { PAY("支付"), DELIVER("发货"), CONFIRM_RECEIVE("确认收货"); privatefinal String eventDesc; OrderEvents(String eventDesc) { this.eventDesc = eventDesc; } public String getEventDesc() { return eventDesc; } }

注解 :

1.OrderStates 枚举类定义了订单的四种状态,并为每个状态提供了描述信息,方便后续业务理解和展示。

2.OrderEvents 枚举类定义了订单的三种事件,同样添加了事件描述,使代码更具可读性。

然后,配置状态机:

public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> { @Override public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states)throws Exception { states .withStates() .initial(OrderStates.UNPAID) // 设置初始状态为待支付 .states(EnumSet.allOf(OrderStates.class )); // 添加所有状态 } @Override public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions)throws Exception { transitions .withExternal() // 定义外部转换 .source(OrderStates.UNPAID) // 源状态:待支付 .target(OrderStates.PAID) // 目标状态:已支付 .event(OrderEvents.PAY) // 触发事件:支付 .and() .withExternal() .source(OrderStates.PAID) .target(OrderStates.DELIVERING) .event(OrderEvents.DELIVER) .and() .withExternal() .source(OrderStates.DELIVERING) .target(OrderStates.COMPLETED) .event(OrderEvents.CONFIRM_RECEIVE); } }

注解 :

1.EnumStateMachineConfigurerAdapter 是 Spring 状态机的配置适配器,用于简化状态机的配置过程。

2.在 configure 方法中,通过 withStates() 设置初始状态和所有状态,明确状态机的初始位置和可到达的状态范围。

3.在另一个 configure 方法中,使用 withExternal() 定义外部状态转换规则,依次设置源状态、目标状态和触发事件,清晰地展示了订单在不同状态之间的转换路径。

状态机的使用与监听

在业务逻辑中使用状态机:

public class OrderService { private StateMachine<OrderStates, OrderEvents> stateMachine; public OrderService(StateMachine<OrderStates, OrderEvents> stateMachine) { this.stateMachine = stateMachine; } public void handleEvent(OrderEvents event) { stateMachine.sendEvent(event); // 发送事件,触发状态转换 OrderStatescurrentState= stateMachine.getState().getId(); System.out.println("[状态跟踪] 当前订单状态:" + currentState.getStateDesc()); } }

注解 :

1.通过构造方法注入状态机实例,使其能够在业务逻辑中使用。

2.handleEvent 方法接收事件并发送给状态机,利用状态机的 sendEvent 方法触发状态转换。

3.获取当前状态并输出其描述信息,便于实时跟踪订单状态变化。

实现状态机监听器:

public class StateMachineListener extends StateMachineListenerAdapter<OrderStates, OrderEvents> { @Override public void transition(Transition<OrderStates, OrderEvents> transition) { System.out.printf("[状态机日志] 开始转换:%s -> %s (事件:%s)%n", transition.getSource().getId().getStateDesc(), transition.getTarget().getId().getStateDesc(), transition.getTrigger().getEvent().getEventDesc()); } @Override public void stateChanged(State<OrderStates, OrderEvents> from, State<OrderStates, OrderEvents> to) { System.out.println("[状态机日志] 转换完成,当前状态:" + to.getId().getStateDesc()); } }

注解 :

1.继承 StateMachineListenerAdapter 类,重写 transition 和 stateChanged 方法,实现对状态转换过程的监听。

2.在 transition 方法中,获取转换的源状态、目标状态和触发事件的描述信息,输出转换开始日志。

3.在 stateChanged 方法中,输出转换完成后的当前状态描述,方便对状态变化进行监控和记录。

Spring 状态机通过规范化的状态管理,能够有效提升复杂业务流程的可维护性。

它将业务流程中的状态和事件进行清晰的定义和管理,使得代码结构更加清晰、易于理解和维护。

以上内容对 Spring 状态机进行了简单介绍,希望对你有所帮助。

编辑
2025-08-11
知识储备-设计模式
00

为什么资深Java开发都爱用策略模式?

一、被if-else支配的恐惧:

我曾踩过的代码坑记得刚接手一个电商项目时,看到支付模块的代码直接懵了——几百行的if-else嵌套,从信用卡支付到三方平台接口,每个条件分支都塞满了逻辑。

最崩溃的是,当产品说"要新增一个跨境支付方式"时,我盯着屏幕半小时不敢下手:改现有逻辑怕影响线上,新增分支又怕代码膨胀成面条。

后来在大佬的指点下用策略模式重构,才发现原来代码可以这么清爽。举个栗子:原本判断用户折扣的代码是这样的:

if (userType == "普通会员") { price = price * 0.9; } else if (userType == "黄金会员") { price = price * 0.8; // 后来又加了铂金会员、钻石会员... } else if (userType == "特邀会员") { price = price * 0.7 + 优惠券抵扣; }

每次新增会员等级都像在雷区跳舞,直到用策略模式把每个折扣规则拆成独立类,才真正体会到"开闭原则"的魅力——新增策略时只要写个新类,原来的代码一行都不用改。

二、策略模式到底是啥?

用快递发货打个比方其实策略模式的核心思想特别接地气。

就像你寄快递时,不同场景会选不同物流:寄急件选顺丰,大件选德邦,便宜货选四通一达。

策略模式就是把这些"发货策略"封装起来,让系统能根据条件动态选择,而不用在代码里写死"如果是急件就选顺丰,否则选...".

在代码里,这个过程分为三步:

定义策略接口:比如ShippingStrategy接口里有个deliver()方法

实现具体策略:SFExpressStrategy、STExpressStrategy等类各自实现发货逻辑

上下文调度:ShippingService根据订单类型调用对应的策略在Spring Boot里集成更方便,靠依赖注入就能把策略塞进上下文里。

我常用的一个技巧是:在策略类上用@Component("sfExpress")这样的注解,然后在上下文里通过Map<String, ShippingStrategy>直接获取,键名就是注解里的字符串,比硬编码if-else爽太多。

三、实战案例:

从混乱到优雅的支付系统重构之前负责的一个跨境电商项目,支付模块支持信用卡、PayPal、本地钱包等七八种方式。

最初的代码是这样的:

if ("creditCard".equals(paymentType)) { // 信用卡支付逻辑,包括3D验证、风控检查... } else if ("paypal".equals(paymentType)) { // PayPal的授权流程,还要处理异步回调... } else if ("localWallet".equals(paymentType)) { // 本地钱包的特殊加密处理... }

每次新增支付方式都要改这个大条件块,更头疼的是单元测试——想测PayPal支付得把整个条件链跑一遍。

后来用策略模式重构:

1.定义支付策略接口

public interface PaymentStrategy { boolean process(PaymentRequest request); }

2.每个支付方式一个策略类

@Component("creditCard") public class CreditCardStrategy implements PaymentStrategy { @Override public boolean process(PaymentRequest request) { // 专注写信用卡支付逻辑,代码量减少60% } }

3.上下文类用Map管理策略

@Service publicclass PaymentService { privatefinal Map<String, PaymentStrategy> strategyMap; @Autowired public PaymentService(Map<String, PaymentStrategy> strategies) { this.strategyMap = strategies; } public boolean pay(PaymentType type, PaymentRequest request) { PaymentStrategy strategy = strategyMap.get(type.name().toLowerCase()); return strategy.process(request); } }

重构后最明显的变化:

新增支付方式时,我只需要写一个新策略类,然后在枚举里加个类型,原来的PaymentService一行都不用动。

测试也变得简单,每个策略类可以单独测,再也不用为了测一个分支跑通整个条件链了。

四、策略模式的隐藏技巧:

Lambda和Spring的神仙组合当策略逻辑比较简单时,甚至可以不用写一堆类。比如计算不同国家的税费,用Java 8的Lambda能把代码缩到极致:

// 在配置类里定义策略Map @Bean public Map<String, Function<Double, Double>> taxStrategyMap() { return Map.of( "CN", amount -> amount * 0.13,  // 中国增值税 "US", amount -> amount * 0.07,  // 美国销售税 "JP", amount -> amount * 0.10   // 日本消费税 ); } // 在服务里直接用 @Service publicclass TaxService { privatefinal Map<String, Function<Double, Double>> strategyMap; @Autowired public TaxService(Map<String, Function<Double, Double>> strategyMap) { this.strategyMap = strategyMap; } public double calculate(String country, double amount) { return strategyMap.get(country).apply(amount); } }

这种写法特别适合没有复杂状态的策略,比如计算规则、汇率转换等,既能享受策略模式的灵活性,又不用写一堆类,简直是懒癌患者的福音。

五、什么时候别用策略模式?

过来人的避坑指南虽然策略模式很香,但也不是万能药。我踩过的坑包括:

小场景别过度设计:如果只有两三种条件判断,比如"是管理员就显示全部按钮,否则显示部分",直接用if-else更直观

有状态的策略要小心:如果策略里需要维护全局状态(比如计数器),多个策略共享时可能出线程安全问题

Spring注入的坑:曾经忘记在策略类上加@Component,结果上下文获取时报空指针,debug半天才发现

还有个小技巧:当策略类很多时,可以用自定义注解来分组。比如我在项目里定义了@PaymentStrategy注解,然后在上下文里通过@Autowired注入List,再结合策略类上的@Order注解排序,比用Map更灵活。

六、从工具到思维:策略模式带来的架构思维升级

刚开始用策略模式时,只是觉得它能干掉讨厌的if-else。但随着项目经验增多,慢慢体会到它背后的设计哲学:把变化的部分封装成可替换的组件,让不变的逻辑保持稳定。

这种思维不仅适用于代码,也适用于架构设计——比如微服务里的路由策略、网关的限流策略,本质上都是策略模式的应用。

现在看到项目里出现超长的条件链,我第一反应就是:这里能不能用策略模式?当产品提出"以后可能会支持XX功能"时,也会下意识地用策略模式预留扩展点。这种思维方式的转变,可能比具体的代码技巧更有价值。

最后想说策略模式不是什么高深的黑魔法,而是从无数次代码维护痛苦中提炼出的解决方案。

它教会我们:好的代码不仅要能跑,还要能优雅地应对变化。下次当你面对满屏的if-else无从下手时,不妨试试策略模式——说不定会打开新世界的大门。

编辑
2025-08-07
工具-Hutool
00

Hutool ExceptionUtil工具使用说明

一、3 个核心功能,解决 80% 的异常处理痛点

1. 异常信息提取:精准定位问题根源

最烦的就是异常被层层包装,比如调用第三方接口时,IOException被包在ServiceException里,又被包在RuntimeException里,想找根因得一层层getCause()。

用 ExceptionUtil 的getCausedBy()一眼看穿: 

try { // 调用可能抛出多层异常的方法 thirdPartyService.syncData(); } catch (Exception e) { // 直接找到最底层的IOException Throwable rootCause = ExceptionUtil.getCausedBy(e, IOException.class); if (rootCause != null) { log.error("数据同步失败,根本原因:{}", rootCause.getMessage()); // 处理IO异常(比如重试) } } 

还有两个常用方法:

getMessage(e):获取完整异常信息(包括所有嵌套异常),日志里记这个准没错

stacktraceToString(e):把堆栈转成字符串,存数据库或日志文件特别方便

2. 异常类型判断:不用再写一堆 instanceof

判断异常类型时,传统写法得堆一堆instanceof: 

// 传统写法:又长又丑 catch (Exception e) { if (e instanceof IOException) { // 处理IO异常 } else if (e instanceof SQLException) { // 处理SQL异常 } else if (e instanceof TimeoutException) { // 处理超时异常 } }

用isCausedBy()一行搞定: 

catch (Exception e) { if (ExceptionUtil.isCausedBy(e, IOException.class)) { log.error("IO异常:{}", e.getMessage()); } else if (ExceptionUtil.isCausedBy(e, SQLException.class)) { log.error("数据库异常:{}", e.getMessage()); } }

3. 受检异常转运行时:告别 try-catch 嵌套地狱

Java 里的受检异常(比如IOException)必须用 try-catch 处理,有时候会导致代码嵌套得像千层饼:

// 传统写法:受检异常逼你加try-catch public String readFile() { try { return Files.readString(Paths.get("config.properties")); } catch (IOException e) { throw new RuntimeException(e); // 手动包装成运行时异常 } } wrapRuntime()一键转换,连 Lambda 都支持: // 一行搞定,代码清爽多了 public String readFile() { return ExceptionUtil.wrapRuntime(() ->  Files.readString(Paths.get("config.properties")) ); }

二、3 个实战场景,看完直接能用

  1. Web 统一异常处理:给用户友好提示,日志记完整堆栈Controller 层用 ExceptionUtil 统一处理异常,既给用户返回友好信息,又在日志里保留完整堆栈: 

`

@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public Result handleException(Exception e) { // 日志里记录完整堆栈,方便排查 log.error("请求异常:{}", ExceptionUtil.stacktraceToString(e)); // 判断异常类型,返回对应提示 if (ExceptionUtil.isCausedBy(e, IllegalArgumentException.class)) { return Result.fail(400, "参数错误:" + e.getMessage()); } else { return Result.fail(500, "系统繁忙,请稍后再试"); } } }

2. 分布式任务:异常堆栈存数据库,方便追溯

定时任务或分布式任务失败时,用stacktraceToString()把堆栈存起来,后续排查问题有依据: 

@Scheduled(cron = "0 0 3 * * ?") public void backupData() { try { // 执行数据备份 dataBackupService.backup(); } catch (Exception e) { // 堆栈转字符串存到日志表 String stackTrace = ExceptionUtil.stacktraceToString(e); taskLogService.saveLog("backupData", "FAILED", stackTrace); // 发送告警 alertService.send("数据备份失败:" + e.getMessage()); } }

3. 第三方接口调用:精准捕获特定异常,针对性处理

调用第三方接口时,经常会遇到各种异常,用getCausedBy()精准定位后针对性处理: 

public void callPaymentApi(PaymentRequest request) { try { thirdPartyPayment.call(request); } catch (Exception e) { // 提取底层异常 Throwable root = ExceptionUtil.getCausedBy(e); if (root instanceof ConnectException) { // 网络连接异常,重试几次 retryCall(request); } elseif (root instanceof TimeoutException) { // 超时异常,记录到待处理队列 pendingQueue.add(request); } else { // 其他异常,直接告警 log.error("支付接口调用失败:{}", ExceptionUtil.getMessage(e)); } } }

三、为什么说它比原生异常处理强 10 倍?

代码量锐减:一行wrapRuntime()替代一堆 try-catch,Lambda 支持更友好

逻辑更清晰:isCausedBy()比一堆instanceof好懂多了,新人也能快速上手

性能优化:支持静态异常和堆栈清除,高并发场景下减少内存占用(比如频繁抛出的已知异常,可清除堆栈节省资源)

异常链透透透:不管异常被包了多少层,getCausedBy()总能找到根因,排查问题效率翻倍

四、最佳实践:这 3 招让异常处理更规范

日志记录要完整:用stacktraceToString()而不是e.getMessage(),避免关键信息丢失

自定义异常结合用:配合 Hutool 的StatefulException(带状态码的异常),实现业务异常分层: 

// 自定义业务异常 public class OrderException extends StatefulException { public OrderException(int code, String message) { super(code, message); } } // 抛出时 if (order.getAmount() <= 0) { throw new OrderException(400, "订单金额必须大于0"); }

封装全局异常工具:把常用操作封装成工具类,全项目统一用法: 

public class ExceptionHandlerUtil { public static void logAndAlert(Exception e, String taskName) { String stackTrace = ExceptionUtil.stacktraceToString(e); log.error("{}失败:{}", taskName, stackTrace); // 发送告警消息 messageService.sendToDingTalk("【" + taskName + "失败】" + e.getMessage()); } }
编辑
2025-08-06
JAVA-Springboot
00

一、Spring Boot 核心注解:项目启动与配置

  1. @SpringBootApplication:项目入口的 “门面担当”

这是 Spring Boot 项目的核心注解,放在主启动类上,一眼就能认出这是个 Spring Boot 项目。它其实是 @Configuration、@EnableAutoConfiguration、@ComponentScan 三个注解的组合体。

@SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }

作用:开启自动配置、组件扫描和配置类功能,少了它 Spring Boot 就没法正常启动。

  1. @Configuration:替代 XML 配置文件

标记一个类是配置类,相当于传统的 XML 配置文件,里面可以用 @Bean 注解定义 bean。

@Configuration public class AppConfig { // 定义一个 RestTemplate 实例,全局可用 @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }

优势:用 Java 代码写配置,比 XML 更灵活,还能加逻辑判断。

  1. @ComponentScan:指定组件扫描范围

告诉 Spring 要扫描哪些包下的组件(@Controller、@Service 等),@SpringBootApplication 已经包含了它,默认扫描当前类所在的包及其子包。

// 手动指定扫描范围 @SpringBootApplication @ComponentScan(basePackages = {"com.example.controller", "com.example.service"}) public class MyApplication { // ... }

坑点:如果你的组件不在默认扫描范围内,就会注入失败,此时必须手动指定。

二、Web 开发注解:处理 HTTP 请求

  1. @Controller:页面交互的控制器

标记一个类是 Spring MVC 的控制器,负责接收用户请求并返回视图(如 HTML 页面)。

@Controller public class UserController { // 处理 GET 请求,返回 user.html 页面 @GetMapping("/user") public String getUser(Model model) { model.addAttribute("name", "张三"); // 向页面传递数据 return"user"; // 返回 templates 目录下的 user.html } }

5. @RestController:API 接口的 “懒人注解”

这是 @Controller + @ResponseBody 的组合,返回的是 JSON 数据,不用再在每个方法上写 @ResponseBody 了。

@RestController public class ApiController { // 直接返回 JSON 数据 @GetMapping("/api/user") public User getUser() { User user = new User(); user.setName("李四"); user.setAge(25); return user; // 自动转为 JSON } }

适用场景:前后端分离项目的 API 接口,现在 90% 的 Spring Boot 项目都用它。

  1. @RequestMapping:请求映射的 “万能注解”指定方法处理哪些 HTTP 请求,可用于类和方法上,支持 GET、POST 等所有请求方式。

`

@RequestMapping("/user") @RestController public class UserController { // 处理 GET 请求:/user/1 @RequestMapping(value = "/{id}", method = RequestMethod.GET) public User getUserById(@PathVariable Long id) { // ... } }

简化注解:

@GetMapping:处理 GET 请求(常用)

@PostMapping:处理 POST 请求(常用)

@PutMapping:处理 PUT 请求

@DeleteMapping:处理 DELETE 请求

  1. @PathVariable:获取 URL 路径参数

从 URL 路径中提取参数,比如从 /users/123 中获取 123 这个 id。

@GetMapping("/users/{id}") public User getUser(@PathVariable Long id) { return userService.findById(id); } // 多个参数 @GetMapping("/users/{id}/orders/{orderId}") public Order getOrder(@PathVariable Long id, @PathVariable Long orderId) { // ... }

8. @RequestParam:获取 URL 查询参数

获取 URL 中?后面的参数,比如从 /users?page=1&size=10 中获取 page 和 size。

@GetMapping("/users") public Page<User> getUsers( @RequestParam(defaultValue = "1") Integer page, // 默认值 1 @RequestParam(defaultValue = "10") Integer size) { return userService.findPage(page, size); }

常用属性:

required:是否必填(默认 true,不填会报错)

defaultValue:默认值(设置后 required 自动变为 false)

  1. @RequestBody:接收 JSON 请求体

接收请求体中的 JSON 数据,并把它转换成对应的 Java 对象,POST、PUT 请求传递复杂对象时常用。

@PostMapping("/users") public User addUser(@RequestBody User user) { // user 已经是转换好的对象,直接保存即可 return userService.save(user); }

注意:前端必须设置 Content-Type: application/json,否则会报错。

三、依赖注入注解:管理组件关系

  1. @Autowired:自动注入依赖

让 Spring 自动找到合适的 bean 并注入进来,不用自己 new 对象了。

@Service public class UserService { // 自动注入 UserDao @Autowired private UserDao userDao; public User getUserById(Long id) { return userDao.findById(id); } }

坑点:如果有多个同类型的 bean,直接用 @Autowired 会报错,此时要配合 @Qualifier 指定名称。

@Autowired @Qualifier("userDaoImpl") // 指定注入名称为 userDaoImpl 的 bean private UserDao userDao;

11. @Service:标记业务逻辑层

告诉 Spring 这是一个服务层组件,负责处理业务逻辑,会被自动扫描并注入。

@Service public class UserService { // 业务逻辑处理 }

同类注解:@Repository(数据访问层)、@Component(通用组件),它们都是为了让代码分层更清晰。

  1. @Resource:按名称注入依赖

和 @Autowired 类似,但它默认按名称匹配,而 @Autowired 默认按类型匹配。

@Service public class OrderService { // 按名称注入(name 可省略,默认取属性名) @Resource(name = "orderDao") private OrderDao orderDao; }

小技巧:如果你更在意 bean 的名称,用 @Resource 更方便。

四、数据访问注解:操作数据库

  1. @Transactional:声明式事务管理

加在方法或类上,让方法执行在事务中,出现异常时自动回滚,保证数据一致性。

@Service public class OrderService { // 该方法在事务中执行 @Transactional public void createOrder(Order order) { orderDao.save(order); // 保存订单 inventoryService.reduceStock(order.getProductId(), order.getNum()); // 扣减库存 // 如果上面的代码抛出异常,事务会回滚,订单和库存操作都取消 } }

常用属性:

rollbackFor:指定哪些异常会触发回滚(默认只有 RuntimeException 才回滚)

propagation:事务传播行为(如 REQUIRED、SUPPORTS 等)

  1. @Mapper:MyBatis 接口映射

标记一个接口是 MyBatis 的 Mapper 接口,不用写实现类就能直接调用。

@Mapper public interface UserMapper { // 直接写方法,SQL 在 XML 中或用注解写 User selectById(Long id); }

替代方案:在主启动类上用 @MapperScan 扫描整个包,不用每个接口都加 @Mapper。

@SpringBootApplication @MapperScan("com.example.mapper") // 扫描所有 Mapper 接口 public class MyApplication { // ... }

五、配置与参数绑定

  1. @Value:读取配置文件的值

直接读取 application.properties 或 application.yml 中的配置,注入到变量中。

# application.properties app.name=我的应用 app.version=1.0.0 @Component public class AppInfo { // 注入配置值 @Value("${app.name}") private String appName; @Value("${app.version}") private String appVersion; }

小技巧:可以用 {} 拼接字符串,比如 @Value("{app.name}-${app.version}")。

  1. @ConfigurationProperties:批量绑定配置

批量读取配置文件中的属性,绑定到一个类的字段上,比 @Value 更适合读取多个相关配置。

# 数据库配置 db.url=jdbc:mysql://localhost:3306/test db.username=root db.password=123456 @Component @ConfigurationProperties(prefix = "db") // 配置前缀 public class DbConfig { private String url; private String username; private String password; // getter 和 setter 必须有,否则无法绑定 }

优势:支持嵌套属性、校验(加 @Validated),还能在 IDE 中自动提示配置项。

六、其他高频注解

  1. @ResponseBody:返回 JSON 数据

让方法返回的对象自动转为 JSON 数据,一般用在 @Controller 类的方法上。

@Controller public class ApiController { // 返回 JSON 数据 @GetMapping("/api/user") @ResponseBody public User getUser() { // ... } }

注意:@RestController 已经包含了它,所以不用重复加。

  1. @PathVariable:获取 URL 路径参数

从 URL 路径中提取参数,比如从 /users/123 中获取 123 这个 id。

@GetMapping("/users/{id}") public User getUser(@PathVariable Long id) { return userService.findById(id); }

19. @RequestParam:获取查询参数

获取 URL 中?后面的参数,比如从 /users?page=1&size=10 中获取 page 和 size。

@GetMapping("/users") public Page<User> getUsers( @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size) { return userService.findPage(page, size); }

20. @ExceptionHandler:全局异常处理

统一处理控制器中的异常,不用在每个方法中写 try-catch 了。

@RestControllerAdvice // 全局异常处理类 public class GlobalExceptionHandler { // 处理空指针异常 @ExceptionHandler(NullPointerException.class) public Result handleNullPointerException(NullPointerException e) { return Result.error("空指针异常:" + e.getMessage()); } // 处理所有异常(兜底) @ExceptionHandler(Exception.class) public Result handleException(Exception e) { return Result.error("系统异常,请联系管理员"); } }

效果:控制器抛出异常后,会自动被这里的方法捕获并处理,返回统一的错误格式。

编辑
2024-12-23
ELK
00

Filebeat 上传日志到 ELK

安装

在 Windows 上安装 Filebeat 的步骤如下:

1. 下载 Filebeat

  1. 访问 Elastic 官网的下载页面。https://www.elastic.co/downloads/beats/filebeat
  2. 找到适用于 Windows 的 Filebeat 版本,点击下载。

2. 解压文件

  1. 下载完成后,找到压缩文件(通常是 .zip 格式)。
  2. 右键点击压缩文件,选择“解压缩到当前文件夹”或使用其他解压工具解压。
  3. 解压后,您会看到一个名为 filebeat-<version>-windows 的文件夹。

3. 配置 Filebeat

  1. 进入解压后的文件夹。
  2. 找到 filebeat.yml 配置文件。可以使用文本编辑器(如 Notepad++ 或 Visual Studio Code)打开它。
  3. 根据您的需求修改配置文件,例如添加日志输入路径、输出设置等。您可以参考文档最后的配置示例。

4. 安装 Filebeat 服务

在 Windows 上,您可以将 Filebeat 作为服务运行。请按照以下步骤进行:

使用 PowerShell 安装 Filebeat 服务

  1. 打开 PowerShell(以管理员身份运行): 在 Windows 搜索框中输入 PowerShell,右键点击 "Windows PowerShell",选择 "以管理员身份运行"。
  2. 导航到 Filebeat 的安装目录: 使用 cd 命令导航到解压后的 Filebeat 文件夹。例如:
    powershell
    cd "C:\Program Files\Filebeat\filebeat-<version>-windows"
  3. 运行 PowerShell 脚本以安装 Filebeat 服务:
    powershell
    .\install-service-filebeat.ps1
  4. 启动 Filebeat 服务:
    powershell
    Start-Service filebeat
  5. 检查 Filebeat 服务的状态:
    powershell
    Get-Service filebeat

5. 启动 Filebeat 服务

  1. 在同一命令提示符窗口中,运行以下命令以启动 Filebeat 服务:
    powershell
    Start-Service filebeat
  2. 您可以使用以下命令检查服务是否正在运行:
    powershell
    Get-Service filebeat

6. 验证 Filebeat 是否运行

  1. 您可以查看 Filebeat 的日志文件以确认其是否正常运行。日志文件通常位于 C:\Program Files\Filebeat\logs 目录下。
  2. 也可以在 Elasticsearch 中检查数据是否成功写入,使用 Kibana 或直接通过 Elasticsearch 的 API。

7. 停止和卸载 Filebeat 服务

如果需要停止 Filebeat 服务,可以运行:

powershell
Stop-Service filebeat

如果需要卸载 Filebeat 服务,可以运行:

powershell
.\uninstall-service-filebeat.bat

8. 其他注意事项

  • 确保您的 Windows 防火墙或其他安全软件允许 Filebeat 访问网络,尤其是连接到 Elasticsearch 的端口(通常是 9200)。
  • 如果使用的是 Elasticsearch 的安全功能,确保在 Filebeat 的配置中正确设置了用户名和密码。

通过以上步骤,您应该能够在 Windows 上成功安装并运行 Filebeat。

9. Windows Filebeat filebeat.yml 示例

yaml
###################### Filebeat Configuration Example ######################### #=========================== Filebeat inputs ============================= filebeat.inputs: # Apache 日志输入 - type: log enabled: true paths: # 将以下路径改为对应的日志目录,可以使用 * 通配符匹配多个日志文件 - E:/RequirementsDocument/ELK-LOG/install/httpd-2.4.62-240904-win64-VS17/Apache24/logs/access*.log fields: log_type: apache # IIS 日志输入 - type: log enabled: true paths: - C:/inetpub/logs/LogFiles/W3SVC*/u_ex*.log fields: log_type: iis # Tomcat 日志输入 - type: log enabled: true paths: - D:/tomcat8.5.73/logs/catalina*.log fields: log_type: tomcat # nginx 日志输入 - type: log enabled: true paths: - E:/RequirementsDocument/ELK-LOG/logs/nginx/access*.log fields: log_type: nginx #============================= Filebeat modules =============================== filebeat.config.modules: path: ${path.config}/modules.d/*.yml reload.enabled: false #==================== Elasticsearch template setting ========================== setup.template.name: "its-log" setup.template.pattern: "its-log-*" setup.template.settings: index.number_of_shards: 1 index.number_of_replicas: 0 _source.enabled: true setup.template: # Apache 模板 - name: "its-log-apache" pattern: "its-log-apache*" settings: index.number_of_shards: 1 index.number_of_replicas: 0 _source.enabled: true # IIS 模板 - name: "its-log-iis" pattern: "its-log-iis*" settings: index.number_of_shards: 1 index.number_of_replicas: 0 _source.enabled: true # Tomcat 模板 - name: "its-log-tomcat" pattern: "its-log-tomcat*" settings: index.number_of_shards: 1 index.number_of_replicas: 0 _source.enabled: true # nginx模板 - name: "its-log-nginx" pattern: "its-log-nginx*" settings: index.number_of_shards: 1 index.number_of_replicas: 0 _source.enabled: true #============================== Kibana ===================================== setup.kibana: #================================ Outputs ===================================== #-------------------------- Elasticsearch output ------------------------------ output.elasticsearch: # Array of hosts to connect to. hosts: ["10.180.27.36:9200"] index: "its-log-%{[fields.log_type]}-cd-%{+YYYY.MM.dd}" username: "xxxx" password: "xxxxxxxxx" #================================ Procesors ===================================== processors: - add_host_metadata: ~ - add_cloud_metadata: ~ #============================== Xpack Monitoring =============================== number_of_files: 3 close_removed: true clean_removed: true #============================== Logging ===================================== logging: level: info to_files: true files: path: C:\Program Files\Filebeat\logs name: filebeat keepfiles: 7 permissions: 0644

###10 Windows filebeat.yml 示例解释

以下是对 Filebeat 配置文件的逐行解释:

1. Filebeat 输入

yaml
filebeat.inputs: # Apache 日志输入 - type: log enabled: true paths: - E:/RequirementsDocument/ELK-LOG/install/httpd-2.4.62-240904-win64-VS17/Apache24/logs/access*.log fields: log_type: apache
  • filebeat.inputs:定义 Filebeat 的输入源。
  • type: log:指定输入类型为日志文件。
  • enabled: true:启用此输入。
  • paths:定义要读取的日志文件路径,支持通配符(*)。
  • fields:自定义字段,这里定义了 log_typeapache,可以用于后续的索引或处理。

接下来的输入部分(IIS、Tomcat、nginx)与 Apache 输入类似,定义了不同类型的日志源,分别为:

  • IIS 日志:C:/inetpub/logs/LogFiles/W3SVC*/u_ex*.log
  • Tomcat 日志:D:/tomcat8.5.73/logs/catalina*.log
  • nginx 日志:E:/RequirementsDocument/ELK-LOG/logs/nginx/access*.log

2. Filebeat 模块

yaml
filebeat.config.modules: path: ${path.config}/modules.d/*.yml reload.enabled: false
  • filebeat.config.modules:配置 Filebeat 模块。
  • path:指定模块文件的位置。
  • reload.enabled:是否启用模块的动态重载,这里设置为 false

3. Elasticsearch 模板设置

yaml
setup.template.name: "its-log" setup.template.pattern: "its-log-*" setup.template.settings: index.number_of_shards: 1 index.number_of_replicas: 0 _source.enabled: true
  • setup.template.name:设置索引模板的名称。
  • setup.template.pattern:指定匹配的索引模式。
  • setup.template.settings:定义索引的设置:
    • index.number_of_shards:将索引分为 1 个分片。
    • index.number_of_replicas:不创建副本(0)。
    • _source.enabled:启用源字段。

接下来的部分定义了针对不同日志类型(Apache、IIS、Tomcat、nginx)的模板设置,基本上与上述设置相同,都是为不同类型的日志指定索引模板。

4. Kibana 设置

yaml
setup.kibana:
  • setup.kibana:用于配置 Kibana 的地址和设置。虽然没有具体配置,但这表示 Filebeat 将连接到 Kibana。

5. 输出设置

yaml
output.elasticsearch: hosts: ["10.180.27.36:9200"] index: "its-log-%{[fields.log_type]}-cd-%{+YYYY.MM.dd}" username: "xxxx" password: "xxxxxxxx"
  • output.elasticsearch:配置 Filebeat 将数据发送到 Elasticsearch。
  • hosts:指定 Elasticsearch 的主机和端口。
  • index:定义索引名称的模式,使用 fields.log_type 来区分不同日志类型,并按日期格式化。
  • usernamepassword:用于连接 Elasticsearch 的凭据。

6. 处理器

yaml
processors: - add_host_metadata: ~ - add_cloud_metadata: ~
  • processors:用于在发送数据之前处理数据。
  • add_host_metadata:添加主机元数据(如主机名、IP 地址等)。
  • add_cloud_metadata:添加云元数据(如果适用)。

7. Xpack 监控设置

yaml
number_of_files: 3 close_removed: true clean_removed: true
  • 这些设置用于监控 Filebeat 的状态,number_of_files 指定最大文件数,close_removedclean_removed 控制日志文件的关闭和清理行为。

8. 日志设置

yaml
logging: level: info to_files: true files: path: C:\Program Files\Filebeat\logs name: filebeat keepfiles: 7 permissions: 0644
  • logging:配置 Filebeat 的日志设置。
  • level:设置日志级别为 info
  • to_files:将日志输出到文件。
  • files:定义日志文件的相关设置:
    • path:日志文件的存储路径。
    • name:日志文件的名称。
    • keepfiles:保留的日志文件数量。
    • permissions:设置日志文件的权限。

总结

这个配置文件定义了 Filebeat 如何收集不同类型的日志(Apache、IIS、Tomcat、nginx),如何将这些日志发送到 Elasticsearch,并配置了相关的索引模板和日志记录设置。每个部分都可以根据实际需求进行调整,以确保 Filebeat 能够高效地收集和发送日志数据。

Ubuntu 使用 tar.gz 包安装 Filebeat 步骤

1. 下载并解压 Filebeat

首先,确保您已经下载并解压了 Filebeat 的 tar.gz 包。如果还没有,可以使用以下命令下载并解压:

bash
# 下载 Filebeat tar.gz 包(请根据需要替换版本号) wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-6.8.23-linux-x86_64.tar.gz # 解压缩 tar xzvf filebeat-6.8.23-linux-x86_64.tar.gz # 进入解压后的目录 cd filebeat-6.8.23-linux-x86_64

2. 配置 Filebeat

在解压后的目录中,您会找到 filebeat.yml 配置文件。您需要根据您的需求编辑此文件(filebeat.yml示例请参考上面windows版本):

bash
vim filebeat.yml

在安装 6.8.23 版本时,可能会出现权限问题,请在 filebeat.yml 文件中添加以下配置:

yaml
seccomp: default_action: allow syscalls: - action: allow names: - rseq

3. 运行 Filebeat

要在后台运行 Filebeat,您可以使用 nohup 命令或将其作为系统服务运行。以下是两种方法:

方法 1:使用 nohup

nohup 命令允许您在退出终端后继续运行程序。您可以使用以下命令在后台运行 Filebeat:

bash
nohup ./filebeat -e -c filebeat.yml > filebeat.log 2>&1 &
  • -e:表示将日志输出到标准错误。
  • -c filebeat.yml:指定配置文件。
  • > filebeat.log 2>&1:将输出和错误重定向到 filebeat.log 文件。
  • &:将命令放入后台运行。

您可以使用以下命令查看 Filebeat 的日志输出:

bash
tail -f filebeat.log

方法 2:使用系统服务(推荐)

如果您希望更好地管理 Filebeat,建议将其作为系统服务运行。您可以创建一个系统服务单元文件,使 Filebeat 在系统启动时自动运行。

  1. 创建一个服务文件:

    bash
    sudo nano /etc/systemd/system/filebeat.service
  2. 在文件中添加以下内容:

    ini
    [Unit] Description=Filebeat Documentation=https://www.elastic.co/guide/en/beats/filebeat/current/index.html After=network.target [Service] User=root Group=root ExecStart=/path/to/filebeat/filebeat -e -c /path/to/filebeat/filebeat.yml Restart=always [Install] WantedBy=multi-user.target

    请确保将 /path/to/filebeat/filebeat/path/to/filebeat/filebeat.yml 替换为您实际的 Filebeat 可执行文件和配置文件的路径。

  3. 重新加载 systemd,使其识别新的服务:

    bash
    sudo systemctl daemon-reload
  4. 启动 Filebeat 服务:

    bash
    sudo systemctl start filebeat
  5. 设置 Filebeat 服务在系统启动时自动启动:

    bash
    sudo systemctl enable filebeat
  6. 检查 Filebeat 服务状态:

    bash
    sudo systemctl status filebeat

总结

以上是在使用 tar.gz 包安装 Filebeat 后,在后台运行 Filebeat 的两种方法。使用系统服务的方式更为推荐,因为它提供了更好的管理和监控功能。

使用 systemctl 启动的 Filebeat 服务的日志可以通过 journalctl 命令来查看。以下是查看 Filebeat 日志的步骤:

1. 查看 Filebeat 日志

您可以使用以下命令查看 Filebeat 的日志:

bash
sudo journalctl -u filebeat.service -f
  • -u filebeat.service:指定要查看的服务。
  • -f:实时跟踪日志输出,类似于 tail -f

2. 查看特定时间范围的日志

如果您只想查看特定时间范围内的日志,可以使用 --since--until 选项。例如,要查看今天的日志,可以这样做:

bash
sudo journalctl -u filebeat.service --since "today"

3. 查看最近的日志条目

如果您只想查看最近的几条日志,可以使用 -n 选项,例如查看最近的 50 条日志:

bash
sudo journalctl -u filebeat.service -n 50

4. 过滤日志

您还可以根据关键字过滤日志。例如,如果您想查找包含 "error" 的日志条目,可以使用:

bash
sudo journalctl -u filebeat.service | grep "error"

5. 查看所有日志

如果您想查看所有日志而不限制于 Filebeat 服务,可以直接运行:

bash
sudo journalctl -f

这将显示所有服务的实时日志输出。