2 SpringBoot3【② 常用注解】

SpringBoot摒弃XML配置方式,改为全注解驱动

1. 组件注册

@Configuration、@SpringBootConfiguration
@Bean、@Scope
@Controller、 @Service、@Repository、@Component
@Import

@ComponentScan

以前的步骤:通过spring配置文件,如果纯xml配置,需要写Bean对象,唯一标识id,全类名,然后通过set注入或者其他注入方式注入默认属性值

现在的步骤:
1、@Configuration 编写一个配置类(配置类其实也会被存入IOC容器,可以通过ctrl + 左键点击这个注解发现内嵌了@Component注解)。其实也可以使用上面的@SpringBootConfiguration注解,本质其实一样,所以Spring相关的核心配置使用SpringBoot的,而通用配置使用默认的

2、在配置类中,自定义方法给容器中注册组件。配合@Bean。如果是第三方的我们直接根据类型,写入为配置类的方法,返回它的类型,然后直接return new出来的对象

2.1、@Configuration注解spring 5.2以后多了属性 proxyBeanMethods,可以设置为true或者false,默认为true(效果代表:是不是代理Bean的方法),如果为true说明为代理对象调用方法,我们在获取这个对象的时候,会从容器检查有没有这个类的对象,有就拿,没有就创建 (保持组件单实例)。主要用于解决组件依赖问题。

当不更改这个值,组件在配置类配置依赖可以直接通过set方法然后传入配置类的其他组件的注入方法(即带有@Bean的方法)。我们不想有这种依赖关系,设置成false就是轻量级模式。测试设置为false对于单个Bean从容器获取多次还是单实例的,但依赖的情况下,Bean内部其他的Bean就不是ioc容器的那个了,而是一个新new的。

3、或使用@Import 导入第三方的组件(可以写在组件类(@Conponent,@Controller等)或者配置类上面,与配置类的注解放在一起),在括号中写对应类的 .class字节码文件(默认value值,不用写属性,且是一个数组(可以一次导入多个组件到容器))。或者我们可以使用全类名(推荐其实不管怎么样默认ioc容器注入的组件的id名称仍是全类名。因为我们开发过程中可能会修改依赖,而如果导入IOC的时候如果删掉之前一些现在用不到的依赖,这种情况下,本身靠字符串的全类名不会引起报错,但是使用字节码文件的时候不存在这个类会大面积报错),对应name属性

2. 条件注解

如果注解指定的条件成立,则触发指定行为 能写在配置类,写了组件注解的类或者是配置类内部的@Bean方法上

@ConditionalOnXxx
@ConditionalOnClass:如果类路径中存在这个类,则触发指定行为
@ConditionalOnMissingClass:如果类路径中不存在这个类,则触发指定行为
@ConditionalOnBean:如果容器中存在这个Bean(组件),则触发指定行为
@ConditionalOnMissingBean:如果容器中不存在这个Bean(组件),则触发指定行为

场景:
● 如果存在FastsqlException这个类,给容器中放一个Cat组件,名cat01,
● 否则,就给容器中放一个Dog组件,名dog01
● 如果系统中有dog01这个组件,就给容器中放一个 User组件,名zhangsan
● 否则,就放一个User,名叫lisi

自定义去实现Condition接口,然后自己写规则

@ConditionalOnBean(value=组件类型,name=组件名字):判断容器中是否有这个类型的组件,并且名字是指定的值

@ConditionalOnRepositoryType (org.springframework.boot.autoconfigure.data)
@ConditionalOnDefaultWebSecurity (org.springframework.boot.autoconfigure.security)
@ConditionalOnSingleCandidate (org.springframework.boot.autoconfigure.condition)
@ConditionalOnWebApplication (org.springframework.boot.autoconfigure.condition)
@ConditionalOnWarDeployment (org.springframework.boot.autoconfigure.condition)
@ConditionalOnJndi (org.springframework.boot.autoconfigure.condition)
@ConditionalOnResource (org.springframework.boot.autoconfigure.condition)
@ConditionalOnExpression (org.springframework.boot.autoconfigure.condition)
@ConditionalOnClass (org.springframework.boot.autoconfigure.condition)
@ConditionalOnEnabledResourceChain (org.springframework.boot.autoconfigure. web)
@ConditionalOnMissingClass(org.springframework.boot.autoconfigure.condition)
@ConditionalOnNotWebApplication (org.springframework.boot.autoconfigure.condition)
@ConditionalOnProperty (org.springframework.boot.autoconfigure.condition)
@ConditionalOnCloudPlatform (org.springframework.boot.autoconfigure.condition)
@ConditionalOnBean(org.springframework.boot.autoconfigure.condition)
@ConditionalOnMissingBean(org.springframework.boot.autoconfigure.condition)
@ConditionalOnMissingFilterBean (org.springframework.boot.autoconfigure.web.servlet)
@Profile (org.springframework.context.annotation)
@ConditionalOnInitializedRestarter (org.springframework.boot.devtools.restart)
@ConditionalOnGraphQlSchema (org.springframework.boot.autoconfigure.graphql)
@ConditionalOnJava (org.springframework.boot.autoconfigure.condition)

3. 属性绑定

3.1. 使用方法

  1. 方法① :@ConfigurationProperties: 声明组件的属性和配置文件哪些前缀开始项进行绑定,可以写在组件Bean上,也可以写到配置类中的配置Bean的方法上

    • 将容器中 任意组件(Bean)的属性值配置文件 的配置项的值 进行绑定
      • 1、给容器中注册组件
        • ①在配置类外使用@ConfigurationProperties结合@Component配置类内无需写任何方法
        • ②配置类内使用@Bean结合@ConfigurationProperties配置类外不用写任何注解
      • 2、使用 @ConfigurationProperties 声明组件和配置文件的哪些配置项进行绑定
  2. 方法② : @EnableConfigurationProperties:快速注册注解(这是写于配置类上的注解,使用@Import注解导入的Bean不会进行属性绑定)

    • 场景:SpringBoot 默认只扫描自己主程序所在的包 。如果导入 第三方包即使组件上标注了 @Component、@ConfigurationProperties 注解,也没用 。因为组件都扫描不进来,此时使用这个注解就可以 快速进行属性绑定并把组件注册进容器

当你有一些自定义的配置属性,并且希望将这些属性封装到一个 Java 类中进行管理时,就可以使用 @EnableConfigurationProperties 注解。

假设在 application.properties 文件中有如下配置:

myapp.name=My Application
myapp.version=1.0

定义一个 Java 类来封装这些属性:

@ConfigurationProperties(prefix = "myapp")
public class MyAppProperties {

    private String name;
    private String version;

    // Getters and Setters
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }
}

然后在配置类中使用 @EnableConfigurationProperties 注解来启用这个配置属性类:

@Configuration
@EnableConfigurationProperties(MyAppProperties.class)
public class AppConfig {
    // 配置类中的其他配置
}

这样,Spring Boot 就会自动将 application.properties 中以 myapp 为前缀的属性值绑定到 MyAppProperties 类的对应字段上。

@ConfigurationProperties 注解用于将配置文件中的属性值绑定到 Java Bean 的字段上。通过指定 prefix 属性,可以将配置文件中以该前缀开头的属性值映射到对应的 Java Bean 字段。在需要将配置文件中的属性值封装到 Java 对象中进行统一管理时使用,这样可以提高代码的可维护性和可读性。

上面的 MyAppProperties 类就是使用 @ConfigurationProperties 注解的示例。通过 @ConfigurationProperties(prefix = "myapp"),将 application.properties 中以 myapp 开头的属性值绑定到 MyAppProperties 类的 nameversion 字段上。

综上所述,@EnableConfigurationProperties@ConfigurationProperties 注解通常一起使用,前者用于启用配置属性类,后者用于定义配置属性类并完成属性绑定。

3.2. 一些拓展使用

那假设同时需要激活大量的配置类,那岂不是需要 @EnableConfigurationProperties 中写大量的类?有没有更好的写法

若你需要激活大量的配置类,在 @EnableConfigurationProperties 中罗列大量类会使代码变得冗长且难以维护。下面为你介绍几种更优的处理办法:

  1. 使用包扫描

可以把所有配置属性类放置在同一个包下,然后借助 @ComponentScan 或者 @ConfigurationPropertiesScan 来自动扫描并注册这些配置属性类,这样就无需在 @EnableConfigurationProperties 里逐一列出每个类。

假设你的配置属性类都存于 com.example.configprops 包中:

@Configuration
@ConfigurationPropertiesScan("com.example.configprops")
public class AppConfig {
    // 其他配置
}

在这个示例中,@ConfigurationPropertiesScan 注解会自动扫描 com.example.configprops 包下所有带有 @ConfigurationProperties 注解的类,并将它们注册到 Spring 容器中。

  1. 组合配置类

你可以把多个配置属性类组合到一个配置类里,然后仅在 @EnableConfigurationProperties 中指定这个组合配置类。

示例代码

首先定义多个配置属性类:

@ConfigurationProperties(prefix = "app.db")
public class DatabaseProperties {
    private String url;
    private String username;
    private String password;

    // Getters and Setters
    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

@ConfigurationProperties(prefix = "app.cache")
public class CacheProperties {
    private String type;
    private int expiration;

    // Getters and Setters
    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public int getExpiration() {
        return expiration;
    }

    public void setExpiration(int expiration) {
        this.expiration = expiration;
    }
}

接着创建一个组合配置类:

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties({DatabaseProperties.class, CacheProperties.class})
public class CombinedConfigProperties {
    // 可以添加其他配置相关的方法或逻辑
}

最后,在主配置类中只需启用这个组合配置类:

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(CombinedConfigProperties.class)
public class MainAppConfig {
    // 其他配置
}
  1. 借助 Java 配置类的模块化

把相关的配置属性类分组到不同的 Java 配置类中,然后在主配置类中引入这些模块化的配置类。

示例代码

创建不同的模块化配置类:

@Configuration
@EnableConfigurationProperties(DatabaseProperties.class)
public class DatabaseConfig {
    // 数据库相关的其他配置
}

@Configuration
@EnableConfigurationProperties(CacheProperties.class)
public class CacheConfig {
    // 缓存相关的其他配置
}

在主配置类中引入这些模块化配置类:

@Configuration
@Import({DatabaseConfig.class, CacheConfig.class})
public class MainConfig {
    // 其他配置
}

通过上述方法,你能够更有效地管理和激活大量的配置类,让代码更具可读性和可维护性。

4. @Scope

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
    
   @AliasFor("scopeName")
   String value() default "";
 
   @AliasFor("value")
   String scopeName() default "";
   
   ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}

scopeName
scopeName是为了声明Bean的作用域,在Spring4.2版本以前,有singleton, prototype两种模式,4.2之后新加了web作用域(request, session, globalsession)。

singleton单例模式,顾名思义即Spring IOC容器对于一个Bean,只会有一个共享的Bean实例。这一个单一的实例会被存储到单例缓存(singleton cache)中,当有请求或者是引用时,IOC容器都会返回存储在singleton cache的同一个实例。

prototype多实例模式,即当每次客户端向容器获取Bean的时候,IOC容器都会新建一个实例并返回。与单例不同的是,在IOC容器启动的时候并不会创建Bean的实例,并且在有请求创建Bean实例之后也不会管理该实例的生命周期,而是由客户端自行处理。

request:web应用针对每一次HTTP请求都会创建一个新的Bean实例,且该实例仅在该次HTTP请求有效。

session:针对每一个session会创建一个Bean实例,且生命周期为该session有效期间。

globalsession:仅基于portlet的web应用才有意义,否则可以当作session使用。

这五种scopeName的使用方法 (也可以直接写对应的字符串,无视大小写)

@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON)
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Scope(scopeName = WebApplicationContext.SCOPE_REQUEST)
@Scope(scopeName = WebApplicationContext.SCOPE_SESSION)
@Scope(scopeName = "globalSession")

ScopeProxyMode
proxyMode表明了@Scope注解的Bean是否需要代理。

  • DEFAULT:proxyMode的默认值,一般情况下等同于NO,即不需要动态代理。
  • NO:不需要动态代理,即返回的是Bean的实例对象。
  • INTERFACES:代理的对象是一个接口,即@Scope的作用对象是接口,这种情况是基于jdk实现的动态代理。
  • TARGET_CLASS:代理的对象是一个类,即@Scope的作用对象是一个类,是以生成目标类扩展的方式创建代理,基于CGLib实现动态代理。 我的Spring6笔记,可以复习动态代理的知识
@Scope(proxyMode = ScopedProxyMode.DEFAULT)
@Scope(proxyMode = ScopedProxyMode.NO)
@Scope(proxyMode = ScopedProxyMode.INTERFACES)
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)

4.1. 单例 Bean 依赖非单例 Bean 的场景

当一个单例 Bean 依赖一个非单例(如原型 prototype)的 Bean 时,如果不设置 proxyMode,单例 Bean 在创建时会注入一个非单例 Bean 的实例,后续无论何时使用这个注入的实例,都是同一个,这就无法体现非单例 Bean 的特性。通过设置合适的 proxyMode 可以确保每次调用单例 Bean 中依赖的非单例 Bean 的方法时,都会获取到一个新的非单例 Bean 实例。

示例代码

// 非单例 Bean
class PrototypeBean {
    private int count = 0;

    public void increment() {
        count++;
        System.out.println("Count: " + count);
    }
}

// 单例 Bean
class SingletonBean {
    private PrototypeBean prototypeBean;

    public SingletonBean(PrototypeBean prototypeBean) {
        this.prototypeBean = prototypeBean;
    }

    public void doSomething() {
        prototypeBean.increment();
    }
}

@Configuration
public class AppConfig {

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public PrototypeBean prototypeBean() {
        return new PrototypeBean();
    }

    @Bean
    public SingletonBean singletonBean(PrototypeBean prototypeBean) {
        return new SingletonBean(prototypeBean);
    }
}

问题分析

在上述代码中,如果不设置 proxyModeSingletonBean 在创建时会注入一个 PrototypeBean 实例,后续每次调用 singletonBean.doSomething() 方法时,使用的都是同一个 PrototypeBean 实例,count 值会不断累加,这与原型 Bean 每次使用都应该是新实例的特性不符。

解决方案

通过设置 proxyMode = ScopedProxyMode.TARGET_CLASS 来为 PrototypeBean 创建代理:

@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public PrototypeBean prototypeBean() {
    return new PrototypeBean();
}

这样,SingletonBean 注入的是 PrototypeBean 的代理对象,每次调用 singletonBean.doSomething() 方法时,都会获取一个新的 PrototypeBean 实例,count 值每次都会从 0 开始。

4.2. Web 应用中的请求和会话作用域

在 Web 应用中,requestsession 作用域的 Bean 通常需要设置 proxyMode。因为单例的控制器或服务 Bean 可能会依赖这些请求或会话作用域的 Bean,如果不设置 proxyMode,会导致在单例 Bean 中无法正确获取不同请求或会话的 Bean 实例。

示例代码

// 请求作用域的 Bean
class RequestScopedBean {
    public void printRequestInfo() {
        System.out.println("This is a request-scoped bean.");
    }
}

// 单例控制器
class MyController {
    private RequestScopedBean requestScopedBean;

    public MyController(RequestScopedBean requestScopedBean) {
        this.requestScopedBean = requestScopedBean;
    }

    public void handleRequest() {
        requestScopedBean.printRequestInfo();
    }
}

@Configuration
public class WebAppConfig {

    @Bean
    @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public RequestScopedBean requestScopedBean() {
        return new RequestScopedBean();
    }

    @Bean
    public MyController myController(RequestScopedBean requestScopedBean) {
        return new MyController(requestScopedBean);
    }
}

解释

在这个示例中,MyController 是单例的,它依赖于 RequestScopedBean。通过设置 proxyMode = ScopedProxyMode.TARGET_CLASSMyController 注入的是 RequestScopedBean 的代理对象,每次处理请求时,都会获取当前请求对应的 RequestScopedBean 实例。

综上所述,当单例 Bean 依赖非单例 Bean 或者在 Web 应用中处理请求和会话作用域的 Bean 时,设置 proxyMode 可以确保 Bean 的作用域特性得到正确体现,避免出现意外的结果。

5. @ImportResource和@Import

如果想把别的xml文件的配置也导入,不想重新手写配置类和注解,可以直接在配置类或者组件类上写@ImportResource("classpath:/xxxxxx.xml")可以把xml的配置文件的组件导入IOC容器

@Import注解提供了三种用法

  1. @Import一个普通类 spring会将该类加载到spring容器中,需要有无参构造器
  2. @Import一个类,该类实现了ImportBeanDefinitionRegistrar接口,在重写的registerBeanDefinitions方法里面,能拿到BeanDefinitionRegistry bd的注册器,能手工往beanDefinitionMap中注册 beanDefinition,这种方式注册既能进行复杂的判断,也能对Bean进行生命周期和名字更改等功能
  3. @Import一个类 该类实现了ImportSelector 重写selectImports方法该方法返回了String[]数组的对象,数组里面的类都会注入到spring容器当中

6. @ComponentScan

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

    ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

    String resourcePattern() default "**/*.class";

    boolean useDefaultFilters() default true;

    Filter[] includeFilters() default {};

    Filter[] excludeFilters() default {};

    boolean lazyInit() default false;

    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Filter {
        FilterType type() default FilterType.ANNOTATION;

        @AliasFor("classes")
        Class<?>[] value() default {};

        @AliasFor("value")
        Class<?>[] classes() default {};

        String[] pattern() default {};
    }
}

1. value

首先它的value值对应要扫描的包名,可以是一个数组,放入要扫描的包名。

2. excludeFilters = {@Filter(type=FilterType.xxxx, classes={xxx,xxx,xxx}) …}

可以填入一个数组,类型为@Filter注解数组,可以排除要扫描的包。可以按注解,或者是类的类型,或者是正则排除【几乎不用】,或者是AspectJ语法【几乎不用】

type=FilterType.ASSUGNABLE_TYPE 按类型排除

如我们不扫描 @Controller 注解的类和 @Service 注解的类

这里我测试发现排除这个注解,并不会把衍生的@RestController注解的组件排除注册

@ComponentScan(excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class,Service.class})
})

3. includeFilters

需要进行设置属性关闭默认扫描规则useDefaultFilters = false

用法和上面没有什么区别

4. 自定义类型扫描

定义一个类,实现TypeFilter接口,实现它的方法

第一个参数metadataReader:可以通过它获取正在扫描的类的信息:如注解,类的信息,类的资源信息。

进一步比如类名,可以用如className.contains("xxx")的方法去判断是否名字包含字符【其实本质不如用上面的正则】

第二个参数metadataReaderFactory可以获得其他的容器组件的信息

使用的时候,只需要@Filter(type=FilterType.CUSTOM,classes={MyFilter})指定使用我们的过滤规则

public class MyFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        return false;
    }
}

7. @Lazy

可以给配置类中加载的Bean上加,即便是单实例Bean,也可以实现懒加载,ioc创建不加载,第一次获取才加载

8. FactoryBean

这里可以参考我的Spring6学习部分的笔记,大同小异,就是实现FactoryBean接口,然后重写方法,主要是补充:

首先,通过配置类注册@Bean返回工厂Bean 类,得到的仍是我们实现类,不是工厂。(这里已经知道了)

补充:要是想从ioc获得工厂而不是这个实现类,getBean方法输入Bean名称字符串,在字符串前面加 &符号。

9. Bean的生命周期

1. 初始化和销毁方法【栈式初始化和销毁(先初始化的后销毁)】

1. xml

Bean以前在基于xml方式的配置的方法中,我们可以在xml配置参数。

现在真的还有用 xml 的人吗?

2. @Bean

而springBoot或者说注解的spring中,我们可以在@Bean通过同名的参数指定方法名。这些方法很重要,我们可以在一些数据源初始化的时候把很多信息注入进去。

别忘了多实例Bean是懒加载,而且每次返回不同的Bean,同时容器也不帮你管理这个Bean容器哪怕销毁,也不会调用这个Beandestroy只是可以帮你创建,单实例被单例池引用,spring容器销毁即销毁,多实例在虚拟机GC时销毁

3. 实现InitializingBeanDisposableBean接口

同时也可以不用这个注解,通过实现InitializingBean接口重写初始化方法,通过实现DisposableBean接口重写销毁方法。

4. 注解方式定义初始化方法和销毁方法【来自JSR250规范来自JDK非Spring

@PostConstruct:在Bean创建完成并属性赋值之后,执行初始化方法

@PreDestroy:在容器销毁之前执行销毁方法

2. Bean的后置处理器: BeanPostProcessor【它也是个组件需要注册】

具体的生命周期过程

  • 1 bean对象创建(调用无参构造器)
  • 2 给bean对象设置属性
  • 3 bean的后置处理器(初始化之前)
  • 4 bean对象初始化(需在配置bean时指定初始化方法)
  • 5 bean的后置处理器(初始化之后)
  • 6 bean对象就绪可以使用
  • 7 bean对象销毁(需在配置bean时指定销毁方法)
  • 8 IOC容器关闭

1. 基本使用

实现BeanPostProcessor接口并重写前置方法和后置方法

Bean的初始化前后进行一些操作。

bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行。

方法会获得Object类型的Bean实例,它的返回值是可以对参数获得的Bean,你可以进行包装或者是直接返回

2. 底层原理

至于源码部分可以看雷神的讲解,总而言之就是底层构建ioc容器的时候,在刷新12大步的时候,会给容器注入Bean,而经历Set属性赋值之后,会for循环遍历所有后置处理器,依次执行后置处理器的前置方法。

Spring底层用它完成了很多事情,可以利用Idea去看看它的实现类,比如之前Spring学过的一个接口,如果想给Bean对象获取ioc容器也很简单,实现ApplicationContextAware接口会默认让重写一个set方法,我们设置一个ApplicationContext类的属性,即可在后置处理器方法实现的过程中,调用postProcessBeforeInitialization方法给Bean对象注入进去。

如还有一个AutowireAnnotationBeanPostProcessor类,它就是容器帮我们进行自动装配的。可以说很多的Spring行为在底层都是基于这个后置处理器完成的

10. @Value

使用这个注解标注在我们的Bean类的字段属性上,可以进行赋值。

  1. 基本数值
  2. 可以写spEL:#{}【很少用】
  3. 可以写${}:取出配置文件中的值【在运行环境变量中的值】

11. @PropertySource

使用这个注解在配置类上可以读取外部配置文件读取到环境变量中。

@PropertySource用于指定外部属性文件的位置,可以将属性文件中的属性值注入到Spring Bean中。它通常与@Value注解一起使用,将属性文件中的值注入到单个属性中。

@ConfigurationProperties用于将属性文件中的值绑定到一个Java对象上。它可以将属性文件中的多个属性值注入到一个Java对象中。与@Value注解不同,@ConfigurationProperties可以将属性文件中的值注入到多个属性中。

12. @AutoWired

详情见之前的Spring笔记有最基础的用法,其实无非就是能放到哪些地方,以及和@Qualifier的配合,默认的一些参数,比如required什么的

这里补充一下一些原理:AutowiredAnnotationBeanPostProcessorBean的生命周期中的后置处理器的前置方法,进行注入。同时如果组件只有一个有参构造器,不需要任何注解标注,就能从容器获取嵌套的内部组件

1. @Primary

注册多个相同类型的Bean,用@Autowired,想要指定只能通过@Qualifier,通过名字指定注入哪个,因为@Autowired是根据类型注入的,但是这么太麻烦了,我们可以在注入的组件的注入注解上再写一个@Primary,即可完成注入的情况,如果发现多个同类型的组件,优先把这个注入。同时这两个是可以一起使用的。

@Qualifier注解的优先级高于@Primary注解。也就是说,当一个接口有多个实现类时,如果使用了@Qualifier注解指定了要注入哪个实现类,那么即使有一个实现类被标记了@Primary注解,也不会被优先选择。

2. @Resource和@Inject【分别是JSR250规范和JSR330规范的Java注解非Spring】

@Resource默认byName注入,没有指定name时把属性名当做name,根据name找不到时,才会byType注入。byType注入时,某种类型的Bean只能有一个。但是不支持Spring@Primary功能和@Autowired默认值功能和required=false功能

想用@Inject需要专门maven依赖,和@Autowired功能一样,支持@Primary了,但是还是不支持@Autowired的默认值功能和required=false功能

3. @Bean标注的方法创建对象的时候,方法参数的值从容器获取【如果有的话】

所以我们不必这种时候写@Autowired注解,如果容器不存在参数写的类型的组件,IDea能识别到,并标识红色波浪号。

13. 想要给Bean组件拿到Spring底层的组件: xxxxxxAware接口

类似给Bean注入ioc容器,实现ApplicationContextAware接口重写set方法即可,其他组件类似。本质其实底层是靠对应的xxxxxxxProcessor后置处理器的postProcessorBeforeInitialization方法实现的

1

14. @Profile

这里后面的Springboot篇章讲了

这里讲讲通过代码的方式,直接通过ioc容器的getEnvironment().setActiveProfiles("xxx", "xxxx"...)可以一次设置单个或多个激活的环境。