SpringBoot 注解功能

配置注解

@Configuration

标识是配置类 = 配置文件, 配置类也是一个组件 默认单实例

参数

proxyBeanMethods 默认true

  1. Full(proxyBeanMethods = true) :proxyBeanMethods 参数设置为 true 时即为:Full 全模式。 该模式下注入容器中的同一个组件无论被取出多少次都是同一个 bean 实例,即单实例对象,在该模式下 SpringBoot 每次启动都会判断检查容器中是否存在该组件。

  2. Lite(proxyBeanMethods = false) :proxyBeanMethods 参数设置为 false 时即为:Lite 轻量级模式。该模式下注入容器中的同一个组件无论被取出多少次都是不同的 bean 实例,即多实例对象,在该模式下 SpringBoot 每次启动会跳过检查容器中是否存在该组件。

什么时候用 Full 全模式,什么时候用 Lite 轻量级模式?

当在你的同一个 Configuration 配置类中,注入到容器中的 bean 实例之间有依赖关系时,建议使用 Full 全模式;当在你的同一个 Configuration 配置类中,注入到容器中的 bean 实例之间没有依赖关系时,建议使用 Lite 轻量级模式,以提高 SpringBoot 的启动速度和性能。

@Bean

简单的例子

// 给容器总添加组件, 使用当前的方法名做为组件名称
// 如果需要改变则可以使用 @Bean("user") 进行改变
@Bean
public User user01() {
    return new User("xiaou", 18);
}

对应的 xml 配置

<beans>
    <bean id="user01" class="com.lean.springboot.bean.User"/>
</beans>

@Bean 也可以依赖其他任意数量的 bean,如果 User 依赖 Car,我们可以通过方法参数实现这个依赖

// 这里会寻找容器中注册过的 car 类型的对象注入的到这里供这个 Bean 创建所需要
@Bean
public User user01(Car car) {
    return new User("xiaou", 18, car);
}
// 指定创建 bean 时候执行的方法的名称
String initMethod() default "";
// 指定销毁 bean 时候方法
String destroyMethod() default "(inferred)";

@Scope Bean 作用域

  1. singleton 全局只有一个实例,即单例模式

  2. prototype 每次注入Bean都是一个新的实例

  3. request 每次HTTP请求都会产生新的Bean

  4. session 每次HTTP请求都会产生新的Bean,该Bean在仅在当前session内有效

  5. global session 每次HTTP请求都会产生新的Bean,该Bean在 当前global Session(基于portlet的web应用中)内有效

@Configuration 和 @Bean 特点

在 Spring 容器中 @Configuration 和 @Bean 都可以对容器注入 Bean,一般情况下在使用 @Configuration 注解的时候都伴随着 @Bean 注解但是不加 @Configuration 也可以对容器中注入 Bean 那么加和不加又有什么区别呢?🤣

实验

java 使用 @Configuration

@Configuration
public class ConfigBean {
    @Bean
    public Users user1() {
        return new Users()
            .setAge(18)
            .setName("xiaou");
    }
}
java 不使用 @Configuration

@Component
public class ConfigBean {
    @Bean
    public Users user1() {
        return new Users().setAge(18).setName("xiaou");
    }
}
java 测试类

@Test void configurationTest() {
    System.out.println(context.getBean(ConfigBean.class));
    System.out.println(context.getBean("user1"));
}
  1. 使用 @Configuration 结果

com.example.springdemo.config.ConfigBean$$EnhancerBySpringCGLIB$$e5f9c687@3b9632d1
Users(name=xiaou, age=18)
  1. 不使用 @Configuration 结果

com.example.springdemo.config.ConfigBean@2f508f3c
Users(name=xiaou, age=18)

发现在使用 @Configuration 注解的时候所标识的类是 CGLIB 代理的, 而没有标识的则没有被 CGLIB 代理. 那么 CGLIB 代理又启到了什么作用。

java 使用 @Configuration

@Configuration
public class ConfigBean {
    @Bean
    public Users user1() {
        System.out.println("user1 被执行了");
        return new Users()
                .setAge(18)
                .setName("xiaou");
    }
    @Bean
    public Users user2() {
        Users users = this.user1();
        return new Users()
                .setName("xiaoz")
                .setAge(2)
                .setFather(users);
    }
    @Bean
    public Users user3() {
        Users users = this.user1();
        return new Users()
                .setName("xiaoy")
                .setAge(4)
                .setFather(users);
    }
}
java 不使用 @Configuration

@Component
public class ConfigBean {
    @Bean
    public Users user1() {
        System.out.println("user1 被执行了");
        return new Users()
                .setAge(18)
                .setName("xiaou");
    }
    @Bean
    public Users user2() {
        Users users = this.user1();
        return new Users()
                .setName("xiaoz")
                .setAge(2)
                .setFather(users);
    }
    @Bean
    public Users user3() {
        Users users = this.user1();
        return new Users()
                .setName("xiaoy")
                .setAge(4)
                .setFather(users);
    }
}
  1. 使用 @Configuration 结果

user1 被执行了
user1 ->Users(name=xiaou, age=18, father=null)
user2 ->Users(name=xiaoz, age=2, father=Users(name=xiaou, age=18, father=null))
user3 ->Users(name=xiaoy, age=4, father=Users(name=xiaou, age=18, father=null))
  1. 不使用 @Configuration

user1 被执行了
user1 被执行了
user1 被执行了
user1 ->Users(name=xiaou, age=18, father=null)
user2 ->Users(name=xiaoz, age=2, father=Users(name=xiaou, age=18, father=null))
user3 ->Users(name=xiaoy, age=4, father=Users(name=xiaou, age=18, father=null))

使用 @Configuration : @Bean 修饰的方法都只被调用了一次, 这个很关键, 因为这样就是产生一个实例在 user2 和user3 中使用的都是 user1 的 bean 。

在不使用 @Configuration: user1 被执行三次分别在 @Bean 注入容器时候, user2, user3 使用 user1 的时候。

这是为什么?

被 @Configuration 修饰的类,Spring 容器中会通过 CGLIB 给这个类创建一个代理,代理会拦截所有被@Bean 修饰的方法,默认情况(bean 为单例)下确保这些方法只被调用一次,从而确保这些 bean 是同一个 bean,即单例的。

@PropertySource 和 @ConfigurationProperties

单独一个 @ConfigurationProperties 注解,表示从默认的全局配置文件中获取值注入而加上 @PropertySource 则可以指定配置文件

@PropertySource(value = {"classpath:user.properties"})
@Component
@ConfigurationProperties(prefix = "person")
public class Person {}

@Lazy

实现 bean 的延迟初始化

  • 延迟初始化:就是使用到的时候才会去进行初始化。

注解定义

@Target({ElementType.TYPE, ElementType.METHOD,
         ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {
    /**
	 * 是否应该发生延迟初始化。
	 */
    boolean value() default true;

}

实验

java 配置类

@Lazy
@Configuration
public class ConfigDemo {
    @Bean
    public String name() {
        System.out.println("create bean >> name");
        return "xiaou";
    }
    public String value() {
        System.out.println("create bean >> value");
        return "xiaoy";
    }
}
java 在类上使用 Lazy

@Component
@Lazy
public class LazyController {
    public LazyController() {
        System.out.println("create->LazyController");
    }
}
@ComponentScan("com.example.springdemo.LazyDemo")
public class LazyBean {}
java 测试方法

@Test
void importTest3() {
    System.out.println("准备启动 Spring 容器");
    AnnotationConfigApplicationContext context =
        new AnnotationConfigApplicationContext(LazyBean.class);
    System.out.println("启动 Spring 容器完成");
    for (String beanName : context.getBeanDefinitionNames()) {
        System.out.println(beanName + "->" + context.getBean(beanName));
    }
}

输出结果

准备启动 Spring 容器
启动 Spring 容器完成
create->LazyController
lazyController->com.example.springdemo.LazyDemo.LazyController@359df09a
create bean >> name
name->xiaou

总结

@ Lazy 可以让 bean 延迟初始化常见用法

  1. 标注在类上

  2. 注在配置类上,会对配置类中所有的 @Bean 标注的方法有效

  3. @Bean 一起标注在方法上

@ImportResource

用于导入 Spring 的 XML 配置文件,让该配置文件中定义的 bean 对象加载到 Spring 容器中。

该注解必须加载 Spring 的主程序入口上。

j@ImportResource(locations = {"classpath:beans.xml"})
@SpringBootApplication
public class HelloWorldApplication {
    public static void main(String[] args) {
        SpringApplication.run(HelloWorldApplication.class, args);
    }
}

@Conditional 条件创建Bean

作用:必须是 @Conditional 指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效

| @Conditional扩展注解 | 作用 |

| ------------------------------- | --------------------------------------------------- |

| @ConditionalOnJava | 系统的 java 版本是否符合要求 |

| @ConditionalOnBean | 容器中存在指定 Bean |

| @ConditionalOnMissingBean | 容器中不存在指定 Bean |

| @ConditionalOnExpression | 满足 SpEL 表达式指定要求 |

| @ConditionalOnClass | 系统中有指定的类 |

| @ConditionalOnMissingClass | 系统中没有指定的类 |

| @ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个 Bean 是首选 Bean |

| @ConditionalOnProperty | 系统中指定的属性是否有指定的值 |

| @ConditionalOnResource | 类路径下是否存在指定资源文件 |

| @ConditionalOnWebApplication | 当前是 web 环境 |

| @ConditionalOnNotWebApplication | 当前不是 web 环境 |

| @ConditionalOnJndi | JNDI 存在指定项 |

自动配置类必须在一定的条件下才能生效,我们可以通过启用 debug=true 属性来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效。

请求注解

@PathVariable

通过 @PathVariable 可以将 URL 中占位符参数 {xxx} 绑定到处理器类的方法形参中 @PathVariable(“xxx“)

参数

| 参数名 | 说明 | |

| -------- | ------------ | ---- |

| value | 路径参数名称 | |

| required | 是否必须 | |

演示

@GetMapping("/init/{id}/{uid}")
public Map<String, Object> init(@PathVariable Integer id, @PathVariable("uid") String userId,
                                @PathVariable Map<String, String> map) {
    Map<String, Object> result = new HashMap<>();
    result.put("id", id);
    result.put("userId", userId);
    result.put("map", map);
    return result;
}

@PathVariable Integer id 必须 id 和上面路径的 id 一致如果不一致则需要使用 @PathVariable("uid") 指定。如果要获取全部的 PathVariable 可以使用 Map<String, String> 定义形参然后使用 @PathVariable 注解修饰。

结果

{
    "id": 11,
    "userId": "22",
    "map": {
        "uid": "22",
        "id": "11"
    }
}

@RequestParam

@RequestParam:将请求参数绑定到你控制器的方法参数上(是springmvc中接收普通参数的注解)

参数

| 参数名 | 说明 |

| ------------ | ------------------------------------------------------------ |

| value | 路径参数名称默认为参数名称 |

| required | 是否必须 |

| defaultValue | 默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值 |

演示

@GetMapping("/params")
public Map<String, Object> params(String name, @RequestParam("uid") Integer userId,
                                  @RequestParam(defaultValue = "false") Boolean status) {
    Map<String, Object> result = new HashMap<>();
    result.put("id", name);
    result.put("userId", userId);
    result.put("status", status);
    return result;
}

结果

{
    "id": "xiaou",
    "userId": 1,
    "status": false
}

这个注解也是可以使用 Map<String, String> 接收所有的 RequestParam 的参数

@RequestHeader

@RequestHeader 用于将 Web 请求头中的数据映射到控制器处理方法的参数中。

参数

| 参数名 | 说明 |

| ------------ | ------------------------------------------------------------ |

| value | 路径参数名称默认为参数名称 |

| required | 是否必须 |

| defaultValue | 默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值 |

演示

@GetMapping("/requestHeader")
public Map<String, Object> requestHeader(@RequestHeader("User-Agent") String userAgent) {
    Map<String, Object> result = new HashMap<>();
    result.put("userAgent", userAgent);
    return result;
}

结果

{
    "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36"
}

这个注解也是可以使用 Map<String, String> 接收所有的 RequestParam 的参数

@CookieValue

@RequestHeader 用于将 Web 请求头中的 cookie 数据取出

参数

| 参数名 | 说明 |

| ------------ | ------------------------------------------------------------ |

| value | 路径参数名称默认为参数名称 |

| required | 是否必须 |

| defaultValue | 默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值 |

演示

@GetMapping("/getCookie")
public Map<String, Object> getCookie(@CookieValue("NMTID") String cookieValue,
                                     @CookieValue("NMTID") Cookie cookie) {
    Map<String, Object> result = new HashMap<>();
    result.put("NMTID_info", cookie);
    result.put("NMTID", cookieValue);
    return result;
}

结果

{
    "NMTID_info": {
        "name": "NMTID",
        "value": "00OQcXWEKqZu6SPVEu2pOFJL8dr6ykAAAF6Rr7CkA",
        "version": 0,
        "comment": null,
        "domain": null,
        "maxAge": -1,
        "path": null,
        "secure": false,
        "httpOnly": false
    },
    "NMTID": "00OQcXWEKqZu6SPVEu2pOFJL8dr6ykAAAF6Rr7CkA"
}

@RequestAttribute

获取 HTTP 的请求(request)对象属性值,用来传递给控制器的参数。

@RequestBody

@RequestBody 主要用来接收前端传递给后端的 json 字符串中的数据的 (请求体中的数据的)

参数

| 参数名 | 说明 |

| ------------ | ------------------------------------------------------------ |

| value | 路径参数名称默认为参数名称 |

| required | 是否必须 |

| defaultValue | 默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值 |

演示

@PostMapping("requestBody")
public Map<String, String> requestBody(@RequestBody Map<String, String> requestHeaderMap) {
    return requestHeaderMap;
}

结果

// 传递 body  Content-Type = "application/json"
{"a":"1","b":"2"}
// 返回
{"a":"1","b":"2"}

1. 一个请求,只有一个RequestBody

2. 当同时使用 @RequestParam()和 @RequestBody 时,@RequestParam()指定的参数可以是普通元素、

数组、集合、对象等等 (即:当,@RequestBody 与 @RequestParam() 可以同时使用时,原 SpringMVC 接收参数的机制不变,只不过 RequestBody 接收的是请求体里面的数据;而 RequestParam 接收的是 key-value里面的参数,所以它会被切面进行处理从而可以用普通元素、数组、集合、对象等接收)。 即:如果参数时放在请求体中,application/json 传入后台的话,那么后台要用 @RequestBody 才能接收到; 如果不是放在请求体中的话,那么后台接收前台传过来的参数时,要用 @RequestParam 来接收,或者形参前 什么也不写也能接收。

@MatrixVariable

矩阵变量可以出现在任何路径片段中,每一个矩阵变量都用分号(;)隔开。比如 “/cars;color=red;year=2012”。多个值可以用逗号隔开,比如 “color=red,green,blue”,或者分开写 “color=red;color=green;color=blue”

开启方法

Springboot 默认是无法使用矩阵变量绑定参数的。需要覆盖 WebMvcConfigurer 中的 configurePathMatch 方法。

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}

参数

| 参数名 | 说明 |

| ------------ | ------------------------------------------------------------ |

| value | 路径参数名称默认为参数名称 |

| required | 是否必须 |

| defaultValue | 默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值 |

| pathVar | 矩阵变量所在的URI路径变量的名称,如果需要消除歧义(例如,在多个路径段中出现同名的矩阵变量)。 |