SpringBoot常用注解

注:以下注解大多引用自springframework,如果引错包(如引用自junit)可能无法达到预期效果

@Pattern

对方法参数或字段进行正则表达式校验

public Result register(
    @Pattern(regexp = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$", message="用户名格式错误") String username,
    @Pattern(regexp = "^.{5,16}$", message="密码格式错误") String password
)

注册Bean

作用相同都是注册Bean对象

1.@Component

(放在类上面)
以下注解均基于@Component实现

注解 继承关系 所在层 核心职责 额外能力
@Component 本体 任意 通用组件
@Controller @Component Web 层 请求分发 MVC 支持
@Service @Component 业务层 业务逻辑 事务承载
@Repository @Component 持久层 数据访问 异常转换

特殊-声明配置类:@Configuration

  • 放在作为配置类的类上
    代理特性:调用配置类中的方法会首先从ICO容器中寻找方法,而不是当做普通的方法去新建new一个

什么时候会触发代理?

只有当 @Configuration(proxyBeanMethods = true) 时,且你 “调用了 @Bean 方法” ,才会触发代理拦截。

2.@Bean

  • 写到方法上面(而非类)
  • 通常写在配置类里面

原理:Spring扫描到@Bean注解时,会执行被注解修饰的方法,并将返回值注入到容器中

可以用来配置第三方Jar包类,将第三方类注入到IOC容器中

@Configuration
public class SpringConfig {
    @Bean
    public IUserService userService(){
        return new UserService();
    }
}

特性:当被修饰的方法有参数时,spring会从容器中寻找该参数并注入

注:若对象已被注册:
1.Bean名称已存在 报错
2.Bean名不存在 不报错

3.@Import(xxx.class)

作用:将xxx.class注入到容器中。相当于额外告诉Spring记得也引用这个类。用作导入配置类或导入普通类。

  • 写到类的上面,修饰一个Bean类

注:
1.被@Import修饰的这个类必须是一个Bean,否则不会起作用。
2.被@Import引用的类是否为Bean对象都可以。
3.被@Import引用的类的Bean名默认是全类名。(若已经被指定,则显示类名>默认规则)

基本用法

通过简单导入将被引用的类注册到Bean

注:被引用的类可以已被注册也可以未被注册

@Configuration
@Import(MyImportSelector.class)//导入目标类
public class SpringConfig {
    @Bean
    public IUserService userService(){
        IUserDao iUserDao = UserDao();
        UserService userService = new UserService();
        return userService;
    }

    @Bean
    public IUserDao UserDao(){
        System.out.println("new UserDao()");
        return new UserDao();
    }
}

进阶用法:ImportSelector接口--批量注册Bean

使用方法:

  1. 创建一个类,继承ImportSelector接口并实现其方法
  2. 批量注册Bean(通过返回包含待注册Bean对象完整引用的字符串数组实现)
  3. 在配置类上@Import(实现ImportSelector的类.class)

目录结构(com.example.c3_ico引用下):

  • config
    • MyImportSelector.java
    • SpringConfig.java
// MyImportSelector.java
package com.example.c3_ico.config;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportSelector implements ImportSelector {
    /*实现该接口的方法*/
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        /*数组要返回的字符串就是需要配置的Bean*/
        return new String[]{
                "com.example.c3_ico.service.UserService",
                "com.example.c3_ico.dao.UserDao",
        };
    }
}
@Configuration
@Import(MyImportSelector.class)//导入该类
public class SpringConfig {
    @Bean
    public IUserService userService(){
        IUserDao iUserDao = UserDao();
        UserService userService = new UserService();
        return userService;
    }

    @Bean
    public IUserDao UserDao(){
        System.out.println("new UserDao()");
        return new UserDao();
    }
}

进阶用法:ImportBeanDefinitionRegistrar 从根本上实现对Bean对象的细微操控

Bean对象的所有属性都会被封装在BeanDefinition。一个Bean对应一个BeanDefinition对象

Bean对象注册流程详见SpringBoot小知识

作用:可以基于底层去做一些扩展

使用方法:

  1. 创建一个类,实现ImportBeanDefinitionRegistrar接口
  2. 根据beanDefinition注册Bean

目录结构(com.example.c3_ico引用下):

  • config
    • MyImportBeanDefinitionRegistrar.java
    • SpringConfig.java
//MyImportBeanDefinitionRegistrar.java
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {  
    @Override  
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {  
  
        /*新建一个引用*/  
        RootBeanDefinition definition = new RootBeanDefinition();  
        /*设置引用的目标类*/  
        definition.setBeanClassName("com.example.c3_ico.service.UserService");  
  
        /*设置一个Bean名并注册*/  
        registry.registerBeanDefinition("userService", definition);  
    }  
}

注:RootBeanDefinitionbeanDefinition接口的实现类

//SpringConfig.java
@Configuration  
@Import(MyImportBeanDefinitionRegistrar.class)  
public class SpringConfig {

}

注:若目标类已被注册为Bean对象则会报错

注入Bean(@Autowired 与 @Resource )

(1)@Autowired与@Resource的使用方法

1. @Autowired 按类型注入

@Component
public class UserService {
    public void hello() {
        System.out.println("Hello from UserService");
    }
}

@Component
public class MyController {

    @Autowired
    private UserService userService;  // 按类型注入

    public void run() {
        userService.hello();
    }
}
  • @Qualifier("BeanName")指定具体Bean
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class MyServiceA {
    // 按类型注入,但因为有多个 GreetingService,需要用 @Qualifier 指定具体 bean
    @Autowired
    @Qualifier("englishGreeting")
    private GreetingService greeting;

    public void say() {
        System.out.println(greeting.greet()); // 输出 "Hello"
    }
}

2. @Resource 按名称优先

@Component("userService")
public class UserService {
    public void hello() {
        System.out.println("Hello from UserService");
    }
}

@Component
public class MyController {

    @Resource(name = "userService")   // 按名称注入
    private UserService service;

    public void run() {
        service.hello();
    }
}

3.@Autowired vs @Resource 匹配规则

@Autowired

  1. 按类型 查找 Bean
  2. 如果有多个同类型 Bean → 按名称匹配(字段名 / 参数名)
  3. 如果仍然不唯一 → 需要配合 @Qualifier@Primary@Autowired

@Resource

  1. 如果写了 @Resource(name="xxx")强制按名称 查找
  2. 如果没写 name
    • 先按 字段名 查找 Bean
    • 如果没找到 → 按 类型 查找
    • 如果有多个同类型 Bean → ❌ 报错

@Autowired特性

  1. @Autowired可以写在构造函数、方法、字段、参数上
  • 构造函数:
    • 如果有无参构造函数,则会默认启用无参构造函数
    • 如果Bean只有一个有参构造函数,是省略@Autowired,会自动注入构造函数的参数
    • 如果有多个有参构造函数,并且没有无参构造函数:会报错。解决方法:使用@Autowired标注在你想指定为默认的构造函数上
  • 属性:
    • 如果想单独指定某个属性为可选注入,则可以单独在属性前加@Autowired(required = false)注解,如下:
@Service  
public class AutoWiredServiceTest {  
  
  
    public AutoWiredServiceTest(@Autowired(required = false) IUserDao userDao) {  
        System.out.println(userDao);  
    }  
    @Autowired  
    public AutoWiredServiceTest(@Autowired(required = false) IUserDao userDao, IUserService userService) {  
        System.out.println("这里是默认构造方法"+userService.toString()+userDao.toString());  
    }  
}
  • 方法:
    • 在方法上添加@Autowired注解可以自动执行方法(哪怕没有主动调用)并注入参数
@Service  
public class AutoWiredServiceTest {  
  
  
    public AutoWiredServiceTest(@Autowired(required = false) IUserDao userDao) {  
        System.out.println(userDao);  
    }  
    @Autowired  
    public AutoWiredServiceTest(@Autowired(required = false) IUserDao userDao, IUserService userService) {  
        System.out.println("这里是默认构造方法"+userService.toString()+userDao.toString());  
    }  
      
    /*在方法上添加Autowired可以自动执行方法并注入参数*/  
    @Autowired  
    public void aaa(IUserDao userDao) {  
        System.out.println("自动执行方法并注入参数"+userDao.toString());  
    }  
}
  • 可选注入:@Autowired(required = false)作用:告诉 Spring:这个依赖“能注入就注入,注不进来也别报错”。注入失败则值为null
  • @Autowired默认会根据类型去容器中找对应对象注入(byType),如果找到了多个会再根据名字找(byName)(但根据名字找可能在低版本并不适用,而是直接报错,所以应该尽量避免重复)。当有多个类型共存时的解决方案:
    • 通过@Primary来设置某一个为主要的。(但可能会因为Bean的扫描顺序而不生效)

注:在构造函数上时,@Autowired(required=false)可选注入会失效。即如果注入参数失败会直接报错

(2)@Inject强制注入(与@Autowired对比分析)

对比点 @Inject @Autowired
所属 Java 标准(JSR-330) Spring 私有
包名 javax.inject / jakarta.inject org.springframework
是否强制依赖 ✅ 必须有 ❌ 可选
是否支持 required=false ❌ 不支持 ✅ 支持
是否支持 @Primary ❌ 不支持 ✅ 支持
是否支持 @Qualifier ✅ 支持 ✅ 支持
推荐使用场景 框架无关 Spring 工程

@RestController 前后端分离时声明返回数据(而非页面)

1️⃣ 作用

@RestController = @Controller + @ResponseBody

👉 方法返回值直接作为 HTTP 响应体(通常是 JSON)

2️⃣ 用在什么地方

👉 前后端分离的接口类(API Controller)

3️⃣ 解决什么问题

  • 避免把返回值当成“页面名”

  • 不需要每个方法都写 @ResponseBody

4️⃣ 什么时候用

✅ 注册 / 登录 / 查询接口

✅ Vue / React / App 调用的接口

5️⃣ 什么时候不用

❌ 返回 HTML 页面(Thymeleaf / JSP)

❌ 传统 MVC 页面控制器


📌 一句话记忆

只要返回的是数据而不是页面,就用 @RestController

示例:

@Controller  
@RestController  
public class UserRegister implements IUserRegister{  
    private UserService userService;  
  
    /*构造器注入*/  
    public UserRegister(UserService userService){  
        this.userService = userService;  
    }  
  
    @Override  
    @PostMapping("/api/register")  
    public CodeResponse register(@RequestBody Map<String, String> map) {  
        return userService.register(map.get("username"),map.get("password"));  
    }  
}

@Value将变量注入spring容器

  • @ConfigurationProperties
  • @Value
  • @PropertySource

1.普通注入

示例:

@Component
public class User{
	@Value("changchang")
	private String name;
	@Value("2774118934")
	private Integer qq;
}

复杂类型(如Map类型、List类型):使用spel表达式

// spel复杂类型
@Value("#{{'key':'value','数学':'100'}}")
private Map<String,Integer> score;
@Value("#{'value1','value2'}")
private List<Stirng> hobbies;

2.设置默认值

@Value("${配置项:默认值}")

当配置项 不存在或为空 时,就会使用冒号后面的默认值。
例:

@Value("${server.port:8080}")
private int port;
  • server.port 存在 → 使用配置值
  • 不存在 → 使用 8080

3.使用非SpringBoot配置文件

方法1:将自定义配置文件加载到Spring(与原配置文件合并)

  1. 自定义文件config/custom.properties
app.name=DemoApp
app.port=9090
  1. 注册到Spring
@Configuration
@PropertySource("classpath:config/custom.properties")
public class CustomConfig {
}
  1. 正常使用@Value(含默认值)
@Value("${app.name:defaultApp}")
private String appName;

@Value("${app.port:8080}")
private int port;

方法2:@ConfigurationProperties + 自定义配置文件(强烈推荐)

  1. 自定义配置文件
custom.name=test
custom.timeout=30
  1. 加载配置
@Configuration
@PropertySource("classpath:custom.properties")
@EnableConfigurationProperties(CustomProperties.class)
public class CustomConfig {
}
  1. 配置类(默认写在字段上)
@ConfigurationProperties(prefix = "custom")
@Data
public class CustomProperties {
    private String name = "default";
    private int timeout = 60;
}

在“配置类”里,直接给字段赋初始值,这个初始值就作为“默认值”。

  • 配置文件 有值 → 用配置文件的
  • 配置文件 没有这个配置项 → 用字段上的默认值
  • 配置文件 完全不存在 → 也用字段默认值

@Order 改变自动注入的顺序

一、@Order 是做什么的(一句话理解)

@Order 用来指定多个同类 Bean 的执行或注入顺序,数值越小,优先级越高。


二、最基本的使用方式

@Order(1)
@Component
public class AService {
}
@Order(2)
@Component
public class BService {
}

执行顺序:

AService → BService

三、@Order 的常见使用场景(重点)

场景 1:多个 Filter 的执行顺序(最常见)

@Order(1)
@Component
public class AuthFilter implements Filter {
}
@Order(2)
@Component
public class LogFilter implements Filter {
}

执行流程:

AuthFilter → LogFilter → Controller

场景 2:多个 HandlerInterceptor 的顺序

@Order(1)
@Component
public class LoginInterceptor implements HandlerInterceptor {
}
@Order(2)
@Component
public class MetricsInterceptor implements HandlerInterceptor {
}

场景 3:同类型 Bean 注入 List 时的顺序

@Autowired
private List<MyHandler> handlers;
@Order(1)
@Component
class AHandler implements MyHandler {
}

@Order(2)
@Component
class BHandler implements MyHandler {
}

handlers 中的顺序为:

AHandler → BHandler

场景 4:ApplicationRunner / CommandLineRunner 执行顺序

@Order(1)
@Component
public class InitDbRunner implements ApplicationRunner {
}
@Order(2)
@Component
public class InitCacheRunner implements ApplicationRunner {
}

四、@Order 的核心规则(记住这 3 条)

规则 1:数值越小,优先级越高

@Order(-1) > @Order(0) > @Order(10)

规则 2:不写 @Order,默认最低优先级

等价于:

@Order(Integer.MAX_VALUE)

规则 3:只对“同一批对象”生效

  • 同接口
  • 同父类
  • 同集合(List / 数组)

不会影响 Bean 的创建或加载顺序

@SpringBootTest指定某个类为测试启动类

1.作用

  • 启动 Spring 容器
  • 加载配置、Bean、自动配置
  • 和你 main 启动的应用几乎一致

2.基本用法

@SpringBootTest
class UserServiceTest {

    @Autowired
    private UserService userService;

    @Test
    void testUser() {
        userService.doSomething();
    }
}

特点:

  • 默认加载整个项目
  • 适合测试:Service + Repository + 配置是否正常
  • 启动慢,但最真实

3.进阶用法

3.1 指定启动类

@SpringBootTest(classes = MyApplication.class)

例:
目录结构:

  • test
    • A.java
    • B.java
    • TestOrder.java
      TestOrder.java
package com.example.springboot1;  
  
import org.junit.jupiter.api.Test;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.boot.test.context.SpringBootTest;  
import org.springframework.context.annotation.Bean;  

//此处没有指定启动类,默认为Application.java,即也默认启用其配置类
@SpringBootTest  
public class TestOrder {  
    @Bean  
    public A a(){  
        return new A();  
    }  
  
    @Bean  
    public B b(){  
        return new B();  
    }  
  
    @Test  
    public void test(@Autowired A a){  
        System.out.println(a);  
    }  
}

该段代码会报错,因为即使给a()注册了Bean对象,在@SpringBootTest处没有指定启动类,默认以Application类启动,即也启用其配置类,而非TestOrder.java本身的配置类

package com.example.springboot1;  
  
import org.junit.jupiter.api.Test;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.boot.test.context.SpringBootTest;  
import org.springframework.context.annotation.Bean;  

//此处指定自身为启动类,即应用自身的配置
@SpringBootTest(classes = TestOrder.class)  
public class TestOrder {  
    @Bean  
    public A a(){  
        return new A();  
    }  
  
    @Bean  
    public B b(){  
        return new B();  
    }  
  
    @Test  
    public void test(@Autowired A a){  
        System.out.println(a);  
    }  
}

注:@SpringBootTest 默认会去找 @SpringBootApplication(或 @SpringBootConfiguration)标注的主启动类,而不是测试类本身。

3.2 使用测试配置

@SpringBootTest(properties = {
    "server.port=0",
    "spring.datasource.url=jdbc:h2:mem:test"
})

覆盖 application.yml测试专用配置

3.3 Web环境配置

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

可选值:

  • NONE:不启动 Web(最快)
  • MOCK(默认):Mock Servlet 环境
  • RANDOM_PORT:随机端口(测试接口常用)
  • DEFINED_PORT:固定端口

@DependsOn 调整Bean的初始化顺序

一、@DependsOn 是什么

@DependsOn 是 Spring 提供的注解,用于显式指定 Bean 的初始化顺序

核心含义:
当前 Bean 必须在指定的 Bean 初始化完成之后 才会被创建。

@DependsOn 的语义是:
指定的 Bean 完全创建完成之后,才创建被标注的 Bean。


二、基本用法

1️⃣ 作用在类上

@Component
@DependsOn("dataSource")
public class UserService {
}

含义:
UserService 初始化之前,dataSource 必须先完成初始化。


2️⃣ 作用在 @Bean 方法上

@Configuration
public class AppConfig {

    @Bean
    public DataSource dataSource() {
        return new HikariDataSource();
    }

    @Bean
    @DependsOn("dataSource")
    public UserService userService() {
        return new UserService();
    }
}

3️⃣ 依赖多个 Bean

@DependsOn({"beanA", "beanB"})

三、@DependsOn 与 @Autowired 的区别

对比点 @DependsOn @Autowired
是否注入对象
是否控制初始化顺序
关注重点 Bean 初始化顺序 Bean 依赖注入

结论:
@DependsOn 只控制“谁先初始化”,不负责对象的注入。


四、典型使用场景

✅ 1. 初始化顺序敏感的组件

  • 数据源(DataSource)
  • Redis / 缓存组件
  • 线程池、全局配置加载器
@DependsOn("redisTemplate")

✅ 2. 隐式依赖场景

代码中没有直接注入,但逻辑上依赖另一个 Bean 的初始化结果,例如:

  • @PostConstruct 中使用全局配置
  • 全局配置由其他 Bean 初始化
@DependsOn("globalConfig")

✅ 3. 第三方 SDK 或旧系统整合

  • SDK 初始化顺序固定
  • 无法通过 @Autowired 明确表达依赖关系

@Lazy 懒加载Bean

  • 正常的Bean对象:Spring容器启动时就加载好
  • 懒加载Bean(@Lazy):在第一次被用到时才去加载。Spring容器启动时不会去new

使用方法

使用@Lazy注解

@SpringBootTest(classes= TestLazy.class)  
public class TestLazy {  
  
    @Bean  
    @Lazy 
    public E e(){  
        return new E();  
    }  
    @Test  
    public void test() {  
  
    }  
}

全局懒加载(Spring Boot)

spring:
  main:
    lazy-initialization: true

效果:

  • 所有 Bean 默认懒加载
  • 常用于大型项目启动加速

使用场景

✅ 适合用懒加载

  • 启动慢的大型系统
  • 很少用到的 Bean
  • 创建成本高的 Bean(连接池、远程调用客户端)
  • 有循环依赖但不想立刻触发

❌ 不建议使用

  • 核心业务 Bean
  • 启动阶段就必须初始化的组件
  • 对首次响应时间极敏感的接口

@Scope 改变Bean的作用域

默认Bean单例(只会new一次,不管@Autowired多少,只会创建一次实例)
好处:节省内存空间(有线程安全风险)

注解可用值

  • 默认单例:@Scope("singleton")
  • 多例(每次注入都会重新创建一个实例):@Scope("prototype")

使用方法

@SpringBootTest(classes= TestScope.class)  
public class TestScope {  
  
    @Bean  
    @Scope("prototype")  
    public F f(){  
        return new F();  
    }  
    @Test  
    public void test(@Autowired F f,@Autowired F ff,@Autowired F fff) {  
        System.out.println(f);  
        System.out.println(ff);  
        System.out.println(fff);  
    }  
}

Bean的创建逻辑

@Scope("prototype") 的 Bean:
👉 容器启动时不会创建
👉 只有在“被真正获取 / 注入”时才创建
👉 每获取一次,创建一个新对象

注:多例会造成内存负担。但有时候一定要用,否则会造成线程不安全

@Conditional及其派生注解:按条件决定某个 Bean / 配置类是否注册到 Spring 容器中

一、@Conditional 注解

1️⃣ 作用

@Conditional 用于按条件决定某个 Bean / 配置类是否注册到 Spring 容器中
是否生效由一个 Condition 条件判断类 决定。

一句话:条件满足 → Bean 生效;条件不满足 → Bean 不创建


2️⃣ 使用方法

  1. 自定义一个类,实现 org.springframework.context.annotation.Condition
  2. 重写 matches() 方法,返回 true / false
  3. @Bean@Configuration 上使用 @Conditional(条件类.class)

3️⃣ 使用场景

  • 根据配置文件决定是否启用功能
  • 根据类是否存在加载 Bean(自动配置核心机制)
  • 根据运行环境(开发 / 生产)
  • 组件的可插拔设计

示例1:简单应用

条件类

public class MyCondition implements Condition {  
    @Override  
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {  
        /*true代表生效*/  
        return true;  
    }  
}

使用 @Conditional

@SpringBootTest(classes = TestConditional.class)  
public class TestConditional {  
    @Bean  
    //必须指定一个实现了Condition接口的类,由matches方法的返回值决定当前Bean是否生效  
    @Conditional(MyCondition.class)  
    public ConditionalService conditionalService() {  
        return new ConditionalService();  
    }  
  
    @Test  
    public void test(@Autowired(required = false) ConditionalService conditionalService){  
        System.out.println(conditionalService);  
    }  
}

示例2:根据引用的依赖来选择性注入(增强插拔性、可维护性)

目录结构:

  • conditionalDB
    • IDB.java
    • MysqlCondition.java
    • MysqlDB.java
    • OracleCondition.java
    • OracleDB.java
    • TestDB.java
      IDB.java
package com.example.springboot1.conditionalDB;  
  
public interface IDB {  
    public void connection();  
}

MysqlCondition.java

package com.example.springboot1.conditionalDB;  
  
import org.springframework.context.annotation.Condition;  
import org.springframework.context.annotation.ConditionContext;  
import org.springframework.core.type.AnnotatedTypeMetadata;  
  
public class MysqlCondition implements Condition {  
    @Override  
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {  
        //是否添加当前数据库依赖  
        //根据类加载,尝试加载MySQL的一个核心类。如果能加载到,说明MySQL依赖被引入  
        try {  
            context.getClassLoader().loadClass("com.mysql.cj.MysqlConnection");  
            return true;  
        } catch (ClassNotFoundException e) {  
            return false;  
        }  
    }  
}

MysqlDB.java

package com.example.springboot1.conditionalDB;  
  
import org.springframework.context.annotation.Conditional;  
  
  
public class MysqlDB implements IDB{  
    @Override  
    public void connection() {  
        System.out.println("连接MySQL数据库");  
    }  
}

OracleCondition.java

package com.example.springboot1.conditionalDB;  
  
import org.springframework.context.annotation.Condition;  
import org.springframework.context.annotation.ConditionContext;  
import org.springframework.core.type.AnnotatedTypeMetadata;  
  
public class OracleCondition implements Condition {  
    @Override  
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {  
        //是否添加当前数据库依赖  
        try {  
            context.getClassLoader().loadClass("oracle.sql.OracleSQLOutput");  
            return true;  
        } catch (ClassNotFoundException e) {  
            return false;  
        }  
    }  
}

OracleDB.java

package com.example.springboot1.conditionalDB;  
  
import org.springframework.context.annotation.Conditional;  
  
  
public class OracleDB implements IDB{  
    @Override  
    public void connection() {  
        System.out.println("连接Oracle数据库");  
    }  
}

TestDB.java

package com.example.springboot1.conditionalDB;  
  
import org.junit.jupiter.api.Test;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.boot.test.context.SpringBootTest;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Conditional;  
  
@SpringBootTest(classes = TestDB.class)  
public class TestDB {  
  
    @Bean  
    @Conditional(MysqlCondition.class)  
    public IDB MysqlDB(){  
        return new MysqlDB();  
    }  
  
    @Bean  
    @Conditional(OracleCondition.class)  
    public IDB OracleDB(){  
        return new OracleDB();  
    }  
  
    @Test  
    public void test(@Autowired IDB idb){  
        System.out.println(idb);  
        idb.connection();  
    }  
}

二、Spring Boot 常用 Conditional 派生注解

这些是 Spring Boot 对 @Conditional 的封装,用得最多

1️⃣ @ConditionalOnProperty

作用

根据配置项是否存在 / 是否为指定值决定 Bean 是否生效

示例

@Bean
@ConditionalOnProperty(name = "feature.login", havingValue = "true")
public LoginService loginService() {
    return new LoginService();
}
feature.login=true

2️⃣ @ConditionalOnMissingBean

作用

当容器中不存在某个 Bean 时才创建

常用于 自动配置允许用户覆盖默认实现

示例

@Bean
@ConditionalOnMissingBean(DataSource.class)
public DataSource defaultDataSource() {
    return new HikariDataSource();
}

如果用户自己定义了 DataSource,该 Bean 不会生效。

3️⃣ @ConditionalOnClass

作用

当 classpath 中存在某个类时才生效

典型用于第三方依赖的自动配置。

示例

@Bean
@ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
public MysqlService mysqlService() {
    return new MysqlService();
}

三、一句话对比总结

注解 判断依据
@Conditional 自定义逻辑
@ConditionalOnProperty 配置文件
@ConditionalOnMissingBean Bean 是否存在
@ConditionalOnClass 类是否存在

@PostConstruct 实现初始化回调方法

在 Bean 的所有依赖属性都注入完成之后,执行一段初始化逻辑。

1.什么是初始化回调方法?从Bean的生命周期说起

Bean的生命周期:

  1. 程序员配置@Component、@Bean...@Autowired等注解,来注册Bean对象
  2. 加载Spring容器
  3. 实例化Bean对象
  4. 解析依赖注入(解析@Autowired @Value等)
  5. 初始化(调用初始化回调方法,由程序员来配置)。关于初始化回调方法的实现,详见:二十、配置初始化回调方法
  6. 最终放入Map<beanName,bean对象>
  7. Spring容器.getBean("beanName")--->Map<beanName,bean对象>

注:从Map中获取Bean对象

  1. Spring容器关闭bean就会销毁调用销毁回调方法,由程序员来配置

2.初始化回调方法的实现

package com.example.springboot1.lifeCallBack;  
  
import jakarta.annotation.PostConstruct;  
import org.springframework.beans.factory.InitializingBean;  
  
public class ChangChangService implements InitializingBean {  
  
    @Override  
    public void afterPropertiesSet() throws Exception {  
        System.out.println("通过实现接口初始化");  
    }  
  
    @PostConstruct  
    public void init() throws Exception{  
        System.out.println("基于注解初始化");  
    }  
    public ChangChangService(){  
        System.out.println("构造方法初始化");  
    }  
}

3.为什么不能把初始化逻辑放在构造方法?

这是实现初始化回调的根本原因。

❌ 构造方法的问题

在构造方法执行时:

  • @Autowired 的 Bean 还没注入
  • @Value 的配置还没赋值
  • 可能为 null 或默认值

4.了解更多详见二十、配置初始化回调方法

@PreDestroy实现销毁回调方法

1.销毁回调方法的实现

public class ChangChangService implements DisposableBean {  
  
    public ChangChangService(){  
        System.out.println("构造方法初始化");  
    }  
  
    @Override  
    public void destroy() throws Exception {  
        System.out.println("接口:销毁destroy");  
    }  
  
    @PreDestroy  
    public void destroy2(){  
        System.out.println("接口:销毁destroy");  
    }  
}

2.了解更多详见二十一、配置销毁回调方法

@ComponentScan 让Spring容器扫描指定包

  • 作用:告诉 Spring 容器去扫描指定包及其子包,把带有 @Component@Service@Repository@Controller 等注解的类自动注册为 Bean。
  • 默认行为:如果不指定包路径,默认扫描 注解所在类的包及其子包
  • 使用场景:当你的组件不在启动类所在包下,或者你想自定义扫描范围时,需要显式使用。

示例:

// 服务类
package org.example.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    public void addUser(String name) {
        System.out.println("添加用户:" + name);
    }
}
// 主配置类
package org.example;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("org.example.service") // 指定扫描包
public class AppConfig {
}
// 测试类
package org.example;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestApp {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = context.getBean(UserService.class);
        userService.addUser("张三");
    }
}

输出:

添加用户:张三

✅ 总结:

  • @ComponentScan 决定 Spring 扫描哪些包来注册 Bean。

  • 在 Spring Boot 中,启动类 @SpringBootApplication 已经隐含了 @ComponentScan,不必重复。

AOP编程:面向切口编程

连接点参数类型:JoinPoint。关于连接点参数更多,详见:连接点(Join point)

@Aspect 注解

  • 作用:标记一个类为 切面类(Aspect),告诉 Spring 这是一个包含横切逻辑(如日志、事务、安全等)的类。
  • 特点:
    • 必须配合 Spring 容器管理(通常加上 @Component)。
    • 内部可以定义通知(Advice)方法,通知可以在目标方法执行前、后、异常时执行。
@Aspect
@Component
public class LogAspect {
    // 切面逻辑放在这里
}

@EnableAspectJAutoProxy 启用AOP功能

没有这个注解AOP功能无法使用

SpringBoot会帮助我们自动配置该注解,(在@SpringBootApplication注解内)
但是依然建议加上该注解,一目了然

@Around 环绕通知(前置+后置+异常+返回值)

  • 作用:环绕通知(Around Advice),可在 方法执行前后 做处理,还可以决定是否执行目标方法。
  • 语法:
@Around("切点表达式")
public Object 方法名(ProceedingJoinPoint joinPoint) { ... }

示例:

@Aspect // 标记为切面类  
@Component  
public class LogAspect {
	@Around("execution(* org.example.c4_aop.UserService.*(..))")
	public void log(ProceedingJoinPoint proceedingJoinPoint){
	    //前置通知
	    //...
	
	    try {
		    //前置通知
		    long begin = System.currentTimeMillis();
		    //执行方法并获取返回值
	        Object proceed = proceedingJoinPoint.proceed(); // 执行目标方法
	    } catch (Throwable e) {
		    //异常通知
	        System.out.println("方法执行异常"+e.getMessage());
	    }finally{
		    //后置通知
		    long end = System.currentTimeMillis();
	    }
	
	    //后置通知
	    System.out.println("方法用时:" + (end - begin) + "ms");
	}
}

切点表达式解析

@Around("execution(* org.example.c4_aop.UserService.*(..))")
  • execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
  • 分解:
    1. * :匹配任意返回类型
    2. org.example.c4_aop.UserService.* :匹配 UserService 类的所有方法
    3. (..) :匹配任意数量、任意类型的参数

⚡ 小结:该表达式表示 对 UserService 类的所有方法进行环绕通知

@Before 前置通知,目标方法之前执行

@Aspect // 标记为切面类  
@Component  
public class LogAspect {  
  
    @Before("execution(* org.example.c4_aop.advice.UserService.*(..))")  
    public void before(JoinPoint joinPoint) {  
        //记录当前方法名和参数  
        String name = joinPoint.getSignature().getName();  
        Object[] args = joinPoint.getArgs();  
        System.out.println("方法名:" + name + " 参数:" + Arrays.toString(args));  
  
    }  
}

@After 后置通知,目标方法之后执行

@Aspect // 标记为切面类  
@Component  
public class LogAspect {  
  
    // 后置通知  
    @After("execution(* org.example.c4_aop.advice.UserService.*(..))")  
    public void before(JoinPoint joinPoint) {  
        //记录当前方法名和参数  
        String name = joinPoint.getSignature().getName();  
        Object[] args = joinPoint.getArgs();  
        System.out.println("后置通知:方法名:" + name + " 参数:" + Arrays.toString(args));  
  
    }  
}

即使目标方法出现异常仍会触发

@AfterThrowing 异常通知,目标方法出现了异常时执行

@Aspect // 标记为切面类  
@Component  
public class LogAspect {  
  
    //异常通知  
    @AfterThrowing(value = "execution(* org.example.c4_aop.advice.UserService.*(..))",throwing = "exception")  
	public void afterThrowing(JoinPoint joinPoint,Exception exception) {  
	    System.out.println("异常通知,异常信息:"+exception.getMessage());  
	}
}

@AfterReturning 返回通知,目标方法返回值执行

@Aspect // 标记为切面类  
@Component  
public class LogAspect {  
    //返回通知  
    @AfterReturning(value = "execution(* org.example.c4_aop.advice.UserService.*(..))",returning = "returnValue")  
	public void afterReturn(JoinPoint joinPoint, Object returnValue) {  
	    System.out.println("返回通知,返回值:"+returnValue);  
	}
}

@Pointcut 抽取切点表达式

切点表达式语法详见10.Spring AOP 切点表达式语法(从基础到进阶)

@Aspect // 标记为切面类  
@Component  
public class LogAspect {  
	//抽取切点表达式
    @Pointcut("execution(* org.example.c4_aop.advice.UserService.*(..))")  
    public void pointcut(){}  
  
    @Before("pointcut()")  
    public void before(JoinPoint joinPoint){  
        System.out.println("前置通知");  
    }  
  
}

Web网络传输注解

@RequestBody:

@RequestBody 用来把「请求体里的 JSON 数据」转换成 Java 对象,并绑定到方法参数上。
示例:

@PostMapping("/user")
public String addUser(@RequestBody JsonNode jsonNode) {
    String username = jsonNode.get("username").asText();
    int age = jsonNode.get("age").asInt();
    return "ok";
}


@RestController:

@RestController = @Controller + @ResponseBody

标记接口(非注解)

什么是标记接口?

形如

public interface Serializable {
}

✔ 没方法
✔ 没代码
✔ 只是一个“资格证”

👉 JVM 看到你 实现了它,就允许你被序列化
👉 没实现 → 直接抛异常

1.Serializable接口:让 Java 对象可以被“序列化”,也就是可以被转换成字节流,从而进行传输、存储或缓存。

什么是序列化?

序列化之前:
Result r = new Result();

👉 这是一个只存在于 JVM 内存中的对象
👉 JVM 一关,啥都没了
👉 不能:

  • 存文件
  • 传网络
  • 放缓存

实现 Serializable 之后

public class Result implements Serializable

这个对象就可以被:

  • 写到磁盘(文件)
  • 通过网络发送
  • 存到 Redis
  • 存到 Session
  • JVM 之间传输
package com.example.demo1.common;

import lombok.Data;

import java.io.Serializable;


/**
 * 返回统一结果类
 */
@Data
public class Result implements Serializable {
    private static final long serialVersionUID = 1L;
    //    状态码
    private int code;
    //    提示信息message
    private String msg;
    //    返回的属性类型
    private Object data;

    /**
     * 直接返回成功结果
     * @param data
     * @return
     */
    public static Result success(Object data) {
        return success(200, "操作成功", data);
    }

    /**
     * 自定义返回成功结果
     * @param code
     * @param msg
     * @param data
     * @return
     */
    public static Result success(int code, String msg, Object data) {
        Result r = new Result();
        r.setCode(code);
        r.setMsg(msg);
        r.setData(data);
        return r;
    }

    /**
     * 不带结果直接返回成功。适合不需要向前端传值的时候使用
     * @return
     */
    public static Result success() {
        Result r = new Result();
        r.setCode(200);
        r.setMsg("操作成功");
        return r;
    }

    /**
     * 直接返回失败信息
     * @return
     */
    public static Result error() {
        return error(400, "操作失败", null);
    }

    /**
     * 带参数返回失败信息
     * @param msg
     * @return
     */
    public static Result error(String msg) {
        return error(400, msg, null);
    }

    /**
     * 自定义返回失败信息
     * @param code
     * @param msg
     * @param data
     * @return
     */
    public static Result error(int code, String msg, Object data) {
        Result r = new Result();
        r.setCode(code);
        r.setMsg(msg);
        r.setData(data);
        return r;
    }
}

serialVersionUID 是干嘛的?

private static final long serialVersionUID = 1L;

用于保证“反序列化时版本一致”

Lombok常用注解

Lombok 是一个 Java 编译时工具,通过注解生成 getter、setter、构造方法、toString、equals/hashCode 等样板代码,从而减少样板代码编写量。

@Data 注解

  • 作用:为类自动生成 Getter、Setter、toString、equals、hashCode 方法,同时生成 全参构造方法
  • 特点:
    • 自动生成的 Getter/Setter 是 public
    • 适合普通的 POJO / DTO
    • 依赖 @ToString@EqualsAndHashCode 等组合注解
@Data
public class User {
    private String username;
    private int age;
}

@Getter / @Setter 注解

  • 作用:
    • @Getter:为类的所有字段生成 getter 方法
    • @Setter:为类的所有字段生成 setter 方法
  • 特点:
    • 可以单独用在字段上生成对应方法
    • 支持 AccessLevel 控制生成方法的访问权限
@Getter
@Setter
public class User {
    private String username;
    private int age;
}

@NoArgsConstructor 注解

  • 作用:生成 无参构造方法
  • 特点:
    • 对有 final 字段或非空字段,需要配合 @NoArgsConstructor(force = true) 才能生成
    • 常用于框架反射初始化(如 Jackson、MyBatis)
@NoArgsConstructor
public class User {
    private String username;
    private int age;
}

@AllArgsConstructor 注解

  • 作用:生成 全参构造方法(所有字段作为参数)
  • 特点:
    • 可以快速初始化对象
    • 支持 @Builder 配合使用,便于链式构造
@AllArgsConstructor
public class User {
    private String username;
    private int age;
}

@RequiredArgsConstructor 注解

  • 作用:为 final 字段@NonNull 字段 生成构造方法
  • 特点:
    • 避免手动写大量构造方法
    • 非 final / 非 @NonNull 字段不会包含在构造方法参数中
@RequiredArgsConstructor
public class User {
    @NonNull
    private String username;
    private int age;
}

@Builder 注解

  • 作用:为类提供 构建者模式(Builder Pattern) 的支持
  • 特点:
    • 可以链式设置字段
    • 与 @AllArgsConstructor 配合使用效果最佳
    • 常用于 DTO、复杂对象初始化
@Builder
public class User {
    private String username;
    private int age;
}

// 使用示例
User user = User.builder()
                .username("zhangsan")
                .age(18)
                .build();

@ToString 注解

  • 作用:自动生成 toString 方法
  • 特点:
    • 默认输出所有字段
    • 可以通过 exclude 排除敏感字段(如密码)
    • 可选择是否调用父类 toString 方法
@ToString(exclude = "password")
public class User {
    private String username;
    private String password;
}

@EqualsAndHashCode 注解

  • 作用:自动生成 equals 和 hashCode 方法
  • 特点:
    • 默认比较所有字段
    • 可以排除某些字段
    • 支持调用父类方法
@EqualsAndHashCode(exclude = "id")
public class User {
    private String id;
    private String username;
}

@NonNull 注解

  • 作用:标记字段或方法参数 不能为 null
  • 特点:
    • Lombok 会在构造方法或 setter 中自动生成 null 检查
    • 可防止空指针异常(NPE)
public class User {
    @NonNull
    private String username;
}

@Slf4j 注解

  • 作用:自动为类生成 log 日志对象
  • 特点:
    • 无需手动写 private static final Logger log = LoggerFactory.getLogger(...)
    • 支持各种日志级别(info/debug/error)
@Slf4j
public class UserService {
    public void doSomething() {
        log.info("执行操作");
    }
}

事务

@Transactional 声明式事务

  1. 标注位置

    • 类上:类中所有 public 方法默认有事务

    • 方法上:单独方法启用事务(覆盖类级别设置)

  2. 常用属性

    • propagation:事务传播行为(常用 REQUIRED

    • isolation:隔离级别(常用 DEFAULTREPEATABLE_READ

    • rollbackFor:指定回滚的异常类型(默认只回滚 RuntimeException

    • readOnly:只读事务(查询优化)

  3. 注意事项

    • 只对 public 方法有效

    • 内部自调用不会触发事务

    • 默认只对运行时异常回滚

    • 数据库必须支持事务

配置路由@RequestMapping

package com.example.demo1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class Demo1Application {

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

	@RequestMapping("/hello")
	public String hello(){
		return "Hello World";
	}
}

此时可以通过localhost:8080/hello来访问该函数

同样的,也可以给一个类配置路由

package com.example.demo1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController

@RequestMapping("/api")
public class Demo1Application {

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

	@RequestMapping("/hello")
	public String hello(){
		return "Hello World";
	}

}

此时,用以访问该函数的地址变为了localhost:8080/api/hello

  • @GetMapping:处理GET请求

用法1 自动判断

该注解可以根据请求类型自动匹配

@GetMapping
public String getRequest(){
	return "GET请求已被正常处理";
}
// 函数的访问地址为localhost:8080/

用法2 设置路由

该注解也可以用来设置路由

@GetMapping("/get")
public String getRequest(){
	return "GET请求已被正常处理";
}
// 函数的访问地址为localhost:8080/get

用法3 以restful的形式通过url来传递参数

@GetMapping("/{id}")
public String getRequest(@PathVariable Long id){
	System.out.println(id);
	return "后端接收到了:"+id;
}

用法4 以传统url字符的方式传递参数

// 传统url符号传值就不需要配置路由的变量了
@GetMapping
public String normoalURL(@RequestParam Long id,@RequestParam String name){
	return "传统url符号传参以触发id:"+id+"\tname:"+name;
}
// localhost:8080/?id=1&name=abc
  • @PostMapping:处理POST请求

@PostMapping
public String save(@RequestBody Map<String,String> map){
	System.out.println(map.toString());
	return "POST请求接收成功";
}
  • @PutMapping:处理PUT请求

@PutMapping("/{id}")
public String update(@PathVariable Long id,@RequestBody Map<String,String> map){
	// 使用%s打印是因为通用性更强
	System.out.printf("ID=%s,name=%s",id,map.toString());
	return "PUT请求成功";
}
  • @DeleteMapping:处理delete请求

@DeleteMapping("/{id}")
public String delete(@PathVariable Long id){
	System.out.printf("ID=%s\n",id);
	return "DELETE请求接收成功";
}
  • @PathVariable:Restful形式接收参数的注解

  • @RequestParam:传统url符号形式接收参数的注解


<!-- 可以设置默认值 -->
@RequestParam(defaultValue = "xxx")
  • @RequestBody:通过POST方法接收参数的注解

  • @ComponentScan:当没有设置basepackages时,默认会将当前注解所在类的包当做是扫描包

posted @ 2026-07-02 17:41  畅畅c  阅读(0)  评论(0)    收藏  举报