注解

1. @Bean 注册bean

1.简介

Bean可以说是Spring当中最为重要的概念之一了。

简单来说,Bean就是一个对象,只不过这个对象是由Spring容器来初始化,装配,管理的,因此也可以叫做Spring Bean

@Bean放在方法上面,用处就是告诉spring容器在初始化的时候,使用这个方法生成一个Bean对象

要做好Java, 需要将Bean(豆子)注入Container(咖啡壶)


2.使用

当注解中只传入value一个属性的时候,value属性的名称可以省略;在源码中,我们可以看见,name是value的别名,意味着name和value这两个属性是一个含义。

即:

@Bean("myBean")等同于@Bean(value="myBean"),也等同于@Bean(name="myBean")

通常,@Bean方法在@Configuration类中声明。

java
@Configuration
public class AppConfig {
    @Bean
    public FooService fooService() {
        return new FooService(fooRepository());
    }

    @Bean
    public FooRepository fooRepository() {
        return new JdbcFooRepository(datasource());
    }

    // ...
}

在上面的代码中,我们向Spring容器中注入了两个Bean,当没有显式命名时,自动注册的名称为方法名

使用name属性显式命名如下:

java
@Bean({"b1", "b2"}) // Bean可以用'b1'或者'b2'取到,而非'myBean'
public MyBean myBean() {
    // 此处初始化和配置MyBean对象
    return obj;
}  

3.补充说明

  1. Bean默认为单例模式,即对于一个Bean而言,每次取出的都是同一个对象。

  2. 一些注解可以和@Bean组合使用:

    @Profile 允许选择性包含某些bean
    @Scope 将bean的范围从单例更改为指定的范围
    @Lazy 在使用是才创建bean
    @DependsOn 会在创建此bean前创建特定的其他bean,以及该bean通过直接引用表示的任何依赖关系
    @Primary 用于在注入点级别解决歧义性

2. @Configuration 配置

1.简介

@Bean注解往往和@Configuration配合,前者标注在方法上,后者标注在类上;两者搭配,将Bean注册到容器中。事实上,正如该注解字面上的意思一样,该注解不仅可以注册Bean,基本上配置相关的内容它都有涉及。

而我们可以从@Configuration的源码中看到,它实质上也是一个@Component注解,所以该注解标记的类本身也会被注册成一个Bean


2.使用

java
@Configuration
public class AppConfig {
    @Bean
    public MyBean myBean() {
        // instance, configure and return bean
    }
}

它指示类生成一个或者多个@Bean方法,可以由Spring容器处理,在运行时为这些Bean生成BeanDefination和服务请求。

3. @Component 注册bean

1.简介

从前面我们知道,所谓的Bean其实就是一个个对象;但是@Bean注解是标注在方法上的,意思就是通过方法返回一个对象,那么有没有直接通过类获取对象的呢?当然有,那就是@Component,被该注解标注的类会被注册到当前容器,bean的id就是类名转换为小驼峰。


2.使用

直接标注在类上,Spring扫描时会将其加入容器。

java
@Component // or @Component("myBean")
public class MyClass {
    // write your bean here...
}

Spring常用注册Bean注解:

  • @Component, @Service, @Repository, @Controller注解作用于类上,实质上是一样的,注册类到当前容器,value属性就是BeanName
  • @Configuration注解也作用于类上,该注解通常与**@Bean**配合使用,注册方法返回类型作为对象,用作配置。
  • @Bean用于方法上,该方法需要在**@Configutation**标注的类里面,且方法必须为public

3.补充说明

@Component注解的效果与@Bean的效果类似,也是单例模式。可以搭配的注解也类似,例如@Scope, @Profile, @Primary等等。

4. @Autowired 注入bean

1.简介

我们创建Bean的目的,就是是为了在需要的时候使用,@Autowired注解可以将bean自动注入到类的属性中。@Autowired注解可以直接标注在属性上,也可以标注在构造器,方法,甚至是传入参数上,本质都是调用了setter方法注入

当自动注入的Bean不存在时,Spring会报错;如果希望Bean存在时才注入,可以使用**@Autowired(required=false)**。


2.使用

标注在属性上:

@Autowired
private MyBean myBean;

标注在方法上:

@Autowired
public void setMyBean(MyBean myBean) {
    this.myBean = myBean;
}

标注在构造函数上:

@Autowired
public MyClass(MyBean myBean) {
    this.myBean = myBean;
}

标注在方法参数上:

public void setMyBean(@Autowired MyBean myBean) {
    this.myBean = myBean;
}

3.补充说明

  1. @Autowired 和 @Resource 注解都是作为bean对象注入时使用的,@Autowired是Spring提供的注解,而@Resource是J2EE本身提供的。
  2. @Autowired 首先根据类型去寻找注入的对象,如果有多个再根据名字匹配。
  3. 当名字也无法区分时可以通过@Qulifier通过Bean的名字显式指定,如:
@Autowired
@Qualifier("userServiceImpl1")
private UserService userService;

5. @Qualifier bean区分

1.简介

当同一个类型的Bean创建了多个时,我们可以通过搭配 @Autowired@Qualifier 来确定需要注入的Bean解决混淆。除此以外,@Qualifier 还可以标注在Bean上实现逻辑分组。


2.使用

  • 标注在单个属性上,是根据name去获取Bean:
@Autowired
@Qualifier("bean2")
private MyBean mybean;

@Bean
public MyBean bean1() {
    return new MyBean();
}

@Bean
public MyBean bean2() {
    return new MyBean();
}
  • 标注在集合上,则可以筛选出被 @Qualifier 标注的Bean
@Autowired
private List<User> users; // user1, user2, user3

@Autowired
@Qualifier
private List<User> usersQualifier; // user2, user3

@Bean
public User user1() {
    return new User();
}

@Bean 
@Qualifier
public User user2() {
    return new User();
}

@Bean
@Qualifier
public User user3() {
    return new User();
}

6. @Primary 优先注入

1.简介

当一个接口有多个实现时,我们可以通过给@Autowired注解搭配@Qualifier来注入我们想要的Bean。这里还有另一种情况:Bean之前分优先级顺序,一般情况下我们只会注入默认实现;这个时候可以采用@Primary注解,该注解标注于Bean上,指示了优先注入的类。


2.使用

使用时直接标注在返回Bean的方法上

@Autowired
private MyBean myBean(); // 注入myBean1

@Primary
@Bean
public MyBean myBean1() {
    return new MyBean();
}

@Bean
public MyBean myBean2() {
    return new MyBean();
}

也可以标注在@Component的类上(@Controller, @Service, @Repository也是一样的)

@Primary
@Component
public class MyBean {
    //...
}

7. @Scope bean作用域

1.简介

Spring中的Bean默认是单例模式,在代码各处注入的一个Bean都是同一个实例;我们将在Spring IoC创建的Bean对象的请求可见范围称为作用域。注解@Scope可用于更改Bean的作用域。

2.属性

我们在@Scope 注解的源码中可以看到,它其共有三个属性,分别为

value、scopeName、proxyMode

(其中value和scopeName两个方法作用是一样的)


对于value和scopeName,指的是bean生成的模式。

在给他们赋值的时候,可以直接传入字符串,

例如:@Scope("prototype")

但这种方法字符串拼写错误不容易发现。所以Spring提供了默认的枚举类型,避免我们写错

Spring提供了默认的参数:

传入作用域类型时,可以直接传入字符串,例如:@Scope("prototype"),但这种方法字符串拼写错误不容易发现;Spring提供了默认的参数:

类型 作用
ConfigurableBeanFactory.SCOPE_PROTOTYPE 原型模式,每次调用时创建新对象
ConfigurableBeanFactory.SCOPE_SINGLETON 单例模式,每次调用返回同一个对象
WebApplicationContext.SCOPE_REQUEST 每个请求返回一个新对象
WebApplicationContext.SCOPE_SESSION 每个会话使用一个新对

对于proxyMode,指的是代理类型。

Spring也提供了一个枚举类型

类型 作用
ScopedProxyMode.INTERFACES 使用JDK的动态代理来创建代理对象
ScopedProxyMode.TARGET_CLASS 使用CGLIB来创建代理对象
ScopedProxyMode.NONE 不代理
ScopedProxyMode. DEFAULT 不代理,同上

3.使用

作用域分为:
基本作用域(singleton, prototype)
Web作用域(request, session, globalssion)
由于默认为单例模式,所以需要标注时一般都是使用prototype

@Bean
@Scope("prototype")  // @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public MyBean myBean() {
    return new MyBean();
}

prototype是原型模式,具体可以看设计模式中的"原型模式",每次通过容器的getBean方法获取Bean时,都将产生一个新的Bean实例。

4.补充说明

常见使用误区,单例调用多例:

// SingletonBean是单例的
@Component
public class SingletonBean {
    @Autowired
    private PrototypeBean bean;  
}

@Component
public class XXX {
    // PrototypeBean是多例的原型模式
    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public PrototypeBean getBean(){
        return new PrototypeBean();
    }
}

上面的代码中,外面的SingletonBean默认单例模式,里面的PrototypeBean设置成原型模式(多例)。
这里并不能达到我们每次创建获得新PrototypeBean是多例的实例的效果。
因为@Autowired只会在单例SingletonBean初始化的时候注入一次,再次调用SingletonBean的时候,PrototypeBean不会再创建了(注入时创建,而注入只有一次)。
解决方法1:不使用@Autowired,每次调用多例的时候,直接调用Bean;
解决方法2:Spring的解决办法:设置proxyMode, 每次请求的时候实例化。

两种代理模式的区别:
ScopedProxyMode.INTERFACES: 创建一个JDK代理模式
ScopedProxyMode.TARGET_CLASS: 基于类的代理模式
前者只能将其注入一个接口,后者可以将其注入类。

8. @Lazy 懒加载

1.简介

Spring Boot中Bean默认的作用域为单例模式,而Spring IoC容器会在启动的时候实例化所有单例Bean。熟悉单例模式的朋友也许立刻想到了饿汉模式和懒汉模式。

在Spring Boot中,@Lazy用于指定Bean是否取消预初始化,该注解可以用于直接或者间接用了@Component注解的类,或使用了@Bean的方法。

PS: Spring使用的是单例注册表实现的单例模式。


2.使用

@Lazy注解使用在**@Bean**方法上:

@Bean
@Lazy
public MyBean myBean() {
    return new MyBean();
}

也可以在**@Comfiguration类上使用,该类中所有的@Bean**方法都会延迟初始化

@Configuration
@Lazy
public class MyConfig() {
    @Bean
    public MyBean myBean1() {
        return new MyBean();
    }
    @Bean
    public MyBean myBean2() {
        return new MyBean();
    }
}

在标注了**@Lazy@Configuration类内,在某个@Bean方法上标注@Lazy(false)**,表明这个bean立即初始化。

@Configuration
@Lazy
public class MyConfig() {
    @Bean
    @Lazy(false)
    public MyBean myBean1() {
        return new MyBean();
    }
}

9. @Profile bean分组

1.前言

  • Profile单词本身的意思是"轮廓","侧写",在编程的其他领域,这个词有针对每个用户的数据存储的意思。在Spring Boot当中,这个词用于区分不同的环境,如开发环境,测试环境,生产环境。
  • 该注解可以传入多个字符串,@Profile("prod"), **@Profile({"test","master"})**都是合法的使用,如果有多个条件,需要同时满足

2.使用

一般在**@Configuration**下使用,标注在能返回Bean的类或者方法上,标注的时候填入一个字符串,作为一个场景或者一个区分。

@Configuration
@Profile("dev")
public class MyConfig {
    // your beans here
}

当Profile为dev时,上面的代码才会在Spring中注册MyConfig这个类。

在Profile的名字前加"!",即Profile不激活才注册相应的Bean。

@Bean
@Profile("!test") // test不激活才创建该Bean
public MyBean myBean() {
    return new MyBean();
}

3.激活环境

  1. 配置计算机的环境变量:

export SPRING_PROFILES_ACTIVE=prod

  1. 设置启动参数 -Dspring.profiles.active=test

    允许同时激活多个环境,如:-Dspring.profiles.active=test,master

  2. 在application.yml中配置

  3. 使用**@ActiveProfiles**注解,一般仅用于测试,放在测试类上面

prod, dev, test等都是常用的缩写, 属于自己定义的内容而非Spring提供的定义

10. @ConfigurationProperties

1.简介

Springboot的配置文件,指的是在resources文件夹的application.properties文件,或application.yml文件。

而在类上使用 @ConfigurationProperties 注解,可以获取配置文件中的数据,将其注入类,为类内的成员属性赋值。成员属性的名字需要和配置文件对应。


2.使用

向注解中传入配置文件中的前缀名,如果配置文件如下:

myConfigs:
  config1:
    field1: f1
    field2: f2
    field3: f3

那么代码中的配置类应该这样写:

@Component
@ConfigurationProperties("myConfigs.config1")
public class MyConfig1 {
    String field1;
    String field2;
    String field3;
}

如上所示,field1, field2, field3三个属性就被绑定到了对象上。

注意到我们使用了@Component,实际上我们使用配置类都是将其注入到其他类中,所以我们往往将其注册为Bean。

ignoreInvalidFields默认为false,不合法的属性的属性会默认抛出异常;
ignoreUnknownFields默认为true, 未能识别的属性会被忽略(所以打错了名字就会被忽略了)

@ConfigurationProperties(prefix="config.prefix", ignoreInvalidFields=true, ignoreUnknownFields=false)
public class MyConfig {
    // fields
}

Spring Boot的绑定规则相当宽松,myField, my-field, my_field等都能识别绑定到myField上。

可以给字段设定默认值,这样配置中没有传入时会使用默认值。

@ConfigurationProperties("your.prefix")
public class YourConfig {
    private String field = "Default"
    // setter
}

类的字段必须要有public访问权限的setter方法。

在很多情况下public的setter方法时必须的,使用IDEA的话,这里推荐Alt+Insert(Windows, Mac使用Alt+n)生成;当然,想使用Lombok也可以

11. @Value 赋值

1.前言

使用**@ConfigurationProperties能够将配置注入到整个类中,而@Value**注解放在成员变量上,能够将配置注入到具体的成员变量中,进行更为细粒度的控制。

注意:注解修饰的字段不能为static或者final。


2.使用

使用上有两种形式:

  1. @Value("\${}"),用来加载外部文件中的值;
  2. @Value("#{}"),用于执行SpEl表达式,并将内容赋值给属性。

我们可以获取Spring Boot配置文件(.yml或.properties)中的属性值并将其赋值给指定变量。

@Value("${my.config.field}")
private String value;

SpEL是一种表达式语言,能够动态的运行语句。

@Value("#{1 + 1}")
private Integer value; // 2

拓展内容

1. @Configuration 和 @Component 区别

1.1. 前置:@Bean注解

在解释这两个注解之前,还是要先了解一下@Bean注解,而关于这个注解,我们需要知道以下几点:

  1. @Bean需要放在方法上,并且@Bean主要用在被@Configuration注解的类里。也可以用在@Component注解的类里。
  2. Spring的@Bean注解用于告诉方法,通过这个方法来产生一个Bean对象,然后把这个Bean对象交给Spring管理。因为在Spring中,bean默认是单例的,所以在单例的情况下,这个bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中;
  3. 当我们需要使用@Bean生成的对象的时候,只需要使用类似@Autowired这样的注释取出即可。
  4. 添加的bean的时候,默认的id为这个方法的方法名。可以通过设置@Bean的name属性来手动设置bean的名字;因为spring中bean默认是单例的,如果重名会因为无法识别具体调用哪个bean而报错。
  5. 特殊的,@Bean注解的方法的返回值也可以是void,这样可以在spring容器初始化进行扫描的时候,执行这个方法内的逻辑。可以通过这种方式,对当前类的成员变量进行初始化。

简单来讲,一般情况下,被@Bean注释的方法的用处就是生成一个bean对象,再把这个对象交给Spring容器管理,以便在需要的时候取出使用。

1.2. @Configuration 和 @Component

在spring中,诸如@Component@Configuration@Bean@Import等注解 ,虽然负责的功能不同,但是都被作为配置注解进行处理

而spring的配置注解又可以分为两类,一类称为 LITE模式, 一类称为 FULL模式

@Component是LITE模式,而@Configuration 默认是FULL模式。

LITE模式的配置注解,被其注解的类不会被Spring所代理,就是一个标准类。如果在这个类中有@Bean标注的方法,而在其他地方,某个方法调用了这个bean方法,那就只是普通Java类的方法的调用。我们知道bean方法都是用来创建对象的,那普通的方法调用,最后就会正常的执行这个bean方法,然后返回一个新的对象。

FULL模式的配置注解,被其注解的类会被Spring所代理。如果在这个类中有@Bean标注的方法,而在其他地方,某个方法试图调用这个bean方法,则会被动态代理所拦截,并返回spring容器中的单例对象。

下面的例子能更生动的描述上述情况:

我们创建一个AppConfig类,并在其中创建两个方法,foo和eoo,其中,让eoo调用foo。

@Component
public class AppConfig {
  @Bean
  public Foo foo() {
    System.out.println("foo() invoked...");
    Foo foo = new Foo();
    System.out.println("foo() 方法的 foo hashcode: " + foo.hashCode());
    return foo;
  }

  @Bean
  public Eoo eoo() {
    System.out.println("eoo() invoked...");
    Foo foo = foo();
    System.out.println("eoo() 方法的 foo hashcode: "+ foo.hashCode());
    return new Eoo();
  }

  public static void main(String[] args) {
    AnnotationConfigApplicationContext context =
        new AnnotationConfigApplicationContext(AppConfig.class);
  }
}

执行结果如下

foo() invoked...
foo() 方法的 foo hashcode: 815992954
eoo() invoked...
foo() invoked...
foo() 方法的 foo hashcode: 868737467
eoo() 方法的 foo hashcode: 868737467

而如果我们将上述例子中,类上的 @Component 换成 @Configuration ,就会有下面不同的结果

foo() invoked...
foo() 方法的 foo hashcode: 849373393
eoo() invoked...
eoo() 方法的 foo hashcode: 849373393

可以看到,在第二种方式中,虽然eoo方法试图直接调用foo方法,但是实际上并没有直接执行foo方法,而是被代理类拦截后,直接返回了容器中早就生成好的单例Foo对象。

而在第一种方式中,eoo调用foo就像是调用普通方法,正常的执行,然后返回了一个新的Foo对象。

  • 需要再次强调的是,如果在其他类中,通过@Autowired的方式获取Foo对象或Eoo对象,那不管获取几次,得到的都是同一个Foo对象或Eoo对象。因为使用@Autowired方式是直接从spring容器中获取对象,而@Bean默认是单例的。在上面的例子中不是通过@Autowired方式,而是通过直接执行方法的方式获取,所以才会出现不同。
如人饮水,冷暖自知。
最后更新于 2023-08-05