Spring @Bean Annotaion(每日一解系列)

2020-03-25 · 树下魅狐 · · 本文共3,441个字,预计阅读需要17分钟。

Spring @Bean是一个方法级别的注解,用于产生一个被Spring IoC容器所管理的Bean。通常情况下,@Bean可以与@Configuration@Component注解一起使用(@Configuration@Component是方法级别的注解)。在默认情况下@Bean注解所产生的Bean是单例模式的,此外,@Bean还可以与@Scope,@Lazy,@DependOn@Primary注解一起使用。

1.认识@Bean

在开始深入了解@Bean注解的使用方式之前,先了解一些@Bean的内部细节。下面是Spring官方提供的源代码:

public @interface Bean {
    @AliasFor("name")
    String[] value() default {};

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

    /** @deprecated */
    @Deprecated
    Autowire autowire() default Autowire.NO;

    boolean autowireCandidate() default true;

    String initMethod() default "";

    String destroyMethod() default "(inferred)";
}

通过源码可以知道,在使用@Bean注解时,有下列一些可选项供我们选择:

  • autowireCandidate: 布尔类型,用于限定当前的Bean是否可以自动注入到其他Bean中,默认是true
  • autowire: 已被废弃
  • initMethod: 在初始化Bean实例时需要调用的方法名称。默认没有要调用的方法
  • destroyMethod: 在关闭应用上下文时要在Bean中调用的方法名称,默认不调用任何方法
  • name: 用于指定Bean的名称
  • value: name的别名,效果等同于name

注意

initMethod所指定的方法必须时不带入参的方法,且该方法允许在运行时抛出异常

2.与@Configuration注解搭配使用

在前面的章节中,我们使用@ComponentScan注解完成了Bean的自动注入,现在,将AppConfig.java类中的@ComponentScan注解去掉,通过@Bean注解手动创建Bean。代码如下:

@Configuration
public class AppConfig {

    @Bean
    public MonkeyKing monkeyKing(){
        return new MonkeyKing();
    }

}

在MonkeyKing类中,不再使用@Component进行注解,改类有两个属性:name和alias,代码如下:

public class MonkeyKing {

    private String name;
    private String alias;

    public MonkeyKing(){
        this.name = "孙悟空";
        this.alias = "齐天大圣";
    }
    public void monkeyShowTime(){
        System.out.println(this.name+": 我是"+this.alias+" "+this.name);
    }
}

提示

一开始,不提供getter和setter方法,通过构造器完成属性的复制,并提供一个方法用于输出bean的信息。

接下来,在main()方法中通过AnnotationConfigApplicationContext获取应用上下文,然后获取刚刚注入的Bean并调用monkeyShowTime()方法,输出属性信息。

@SpringBootApplication
public class BeanAnnotationApplication {

    public static void main(String[] args) {
        SpringApplication.run(BeanAnnotationApplication.class, args);
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(AppConfig.class);

        MonkeyKing monkeyKing = context.getBean(MonkeyKing.class);
        monkeyKing.monkeyShowTime();

        context.close();
    }

}

运行结果:

孙悟空: 我是齐天大圣 孙悟空

3.自定义Bean name

在上面例子中,我们是通过Bean的类型从上下文中获取Bean的实例的,除此之外,还可以在@Bean注解中使用name来指定Bean的名称。name接收一个或多个参数。接下来,新创建一个类MarshalCanopy.java

public class MarshalCanopy {

    private String name;
    private String alias;

    public MarshalCanopy(){
        this.name = "猪八戒";
        this.alias = "天蓬元帅";
    }

    public void pigShowTime(){
        System.out.println(this.name+": 大师兄,师父被妖怪抓走了!");
    }
}

现在,在AppConfig类中定义新的Bean并使用name指定多个名称:

@Bean(name = {"pig","bajie"})
public MarshalCanopy marshalCanopy(){
    return new MarshalCanopy();
}

最后,在main()中通过name来获取MarshalCanopy的Bean实例:

MarshalCanopy bajie = (MarshalCanopy) context.getBean("bajie");
MarshalCanopy pig = (MarshalCanopy) context.getBean("pig");

bajie.pigShowTime();
pig.pigShowTime();

运行结果:

猪八戒: 大师兄,师父被妖怪抓走了!
猪八戒: 大师兄,师父被妖怪抓走了!

4. initMethod和destroyMethod

initMethod可以指定Bean在实例化过程中需要调用的方法,destroyMethod可以指定在关闭上下文时Bean需要执行的方法。接下来,在MonkeyKing中增加两个方法birth()和destroy():

public class MonkeyKing {

    private String name;
    private String alias;


    public void birth(){
        System.out.println("美猴王出世...");
    }

    public void destroy(){
        System.out.println("孙猴子被如来压在了五行山下...");
    }

    public MonkeyKing(){
        this.name = "孙悟空";
        this.alias = "齐天大圣";
    }

    public void monkeyShowTime(){
        System.out.println(this.name+": 我是"+this.alias+" "+this.name);
    }
}

然后,修改AppConfig中相应的配置:

@Bean(initMethod = "birth",destroyMethod = "destroy")
public MonkeyKing monkeyKing(){
    return new MonkeyKing();
}

最后,再次运行main()方法看看控制台输出内容:

美猴王出世...
孙悟空: 我是齐天大圣 孙悟空
孙猴子被如来压在了五行山下...

5.Bean之间的引用

在同一个配置中,我们可以在一个Bean的方法中直接调用另外一个Bean的方法。为了演示这一特效,新建一个类Mountain,代码如下:

public class Mountain {

    private String name;
    private MonkeyKing king;

    public Mountain(MonkeyKing king){
        this.name = "花果山";
        this.king = king;
    }

    public void showTime(){
        king.monkeyShowTime();
        System.out.println("住在"+this.name+"水帘洞");
    }
}

接下来,在AppConfig中配置Mountain并直接调用monkeyKing()方法,代码如下:

Configuration
public class AppConfig {
    ...

    @Bean(name = "huaguoshan")
    public Mountain mountain(){
        return new Mountain(monkeyKing());
    }
}

最后,在main()中获取Mountain的Bean实例,并观察控制台输出:

Mountain mountain = (Mountain) context.getBean("huaguoshan");
mountain.showTime();

输出结果:

美猴王出世...
孙悟空: 我是齐天大圣 孙悟空
住在花果山水帘洞
孙猴子被如来压在了五行山下...

6. 与@Scope,@Lazy,@Primary@DependsOn配合使用

@Bean注解可以与Scope,@Lazy,@Primary@DependsOn注解一起配合,对Bean的创建过程进行更细致的控制。

6.1 与@Scope一起使用

在默认情况下,@Bean所定义的Bean属于单例模式(singleton),如果希望修改Bean的这一属性,可以通过@Scope注解进行修改。@Scope注解支持的参数有prototype,request和session。下面是一个简单的案例:

@Configuration
public class AppConfig{
    @Bean
    @Scope("prototype")
    public XBean xbean(){
        return new XBean();
    }
}

6.2 与@Lazy一起使用

Spring IoC (ApplicationContext) 容器一般都会在启动的时候实例化所有单实例 bean 。如果我们想要 Spring 在启动的时候延迟加载 bean,即在调用某个 bean 的时候再去初始化,那么就可以使用 @Lazy 注解。为此,新创建一个类SandMonk:

public class SandMonk {

    private String name;

    private String alias;

    public SandMonk(){
        this.name = "沙悟净";
        this.alias = "沙和尚";
        System.out.println(this.alias+": 大师兄,二师兄,师父被妖怪抓走了");
    }
}

首先,不使用@Lazy注解,观察控制台输出情况:

@Bean
public SandMonk sandMonk(){
    return new SandMonk();
}
@SpringBootApplication
public class BeanAnnotationApplication {

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

}

输出结果:

2020-03-25 16:08:01.187  INFO 13520 --- [           main] c.r.b.a.BeanAnnotationApplication        : Started BeanAnnotationApplication in 1.325 seconds (JVM running for 2.104)
沙和尚: 大师兄,二师兄,师父被妖怪抓走了
Process finished with exit code 0

接下来,将@Lazy作用在sandMonk方法上,再次运行main()方法,观察结果。

@Bean
@Lazy
public SandMonk sandMonk(){
    return new SandMonk();
}

输出结果:

2020-03-25 16:11:40.658  INFO 15092 --- [           main] c.r.b.a.BeanAnnotationApplication        : Started BeanAnnotationApplication in 1.041 seconds (JVM running for 1.813)

Process finished with exit code 0

提示

@Lazy默认value的值就是true,所以省略不写,如果设置成false,则直接不使用@Lazy注解

6.3 与@DependsOn一起使用

该注解用于声明当前bean依赖于另外一个bean。所依赖的bean会被容器确保在当前bean实例化之前被实例化。为了演示,新建一个类TangSeng:

public class TangSeng {
    private String name;
    private String alias;
    public TangSeng(){
        this.name = "唐三藏";
        this.alias = "唐僧";
        System.out.println("name:"+this.name+",alias:"+this.alias);
    }
}

接下来,在AppConfig类中配置此类,首先不使用@DependsOn注解,看看控制台输出情况。

@Bean(name = "tangseng")
public TangSeng tangSeng(){
    return new TangSeng();
}

控制台输出:

name:唐三藏,alias:唐僧
name:孙悟空,alias:齐天大圣
美猴王出世...
name:猪八戒, alias:天蓬元帅

因Spring是顺序加载AppConfig中定义的Bean,所以唐僧被第一个执行,因沙僧是延迟加载,所以控制台并没有输出沙僧信息(ps:存在感太低,不急于马上加载)。现在,在tangSeng()方法上使用@DependsOn注解,让唐僧依赖于悟空,八戒和沙僧,然后再次执行并观察输出结果。

@Bean(name = "tangseng")
@DependsOn(value ={"monkeyKing","bajie","sandMonk"} )
public TangSeng tangSeng(){
    return new TangSeng();
}

控制台输出结果:

name:孙悟空,alias:齐天大圣
name:猪八戒, alias:天蓬元帅
name:沙悟净,alias:沙和尚
name:唐三藏,alias:唐僧

这次我们看到,虽然唐僧是第一个被配置的,但却最后一个被执行,这是因为它需要等其余三个弟子都加载完成,才能保护它西去取经。在第一次中,沙僧并未被执行,而第二次被执行,这是因为存在感再怎么低,但师徒四人在西去的路上一个都不能少。

6.4 与@Primary一起使用

@Primary注解的作用是当应用成存在一个或多个相同类型的Bean时,优先装配被@Primary注解的bean。为了演示,以真假美猴王为案例,创建一个Monkey接口,并让MonkeyKing和MonkeyKingCopy两个类实现该接口。

public interface Monkey {
}
public class MonkeyKing implements Monkey{

    private String name;
    private String alias;


    public void birth(){
        System.out.println("美猴王出世...");
    }

    public void destroy(){
        System.out.println("孙猴子被如来压在了五行山下...");
    }

    public MonkeyKing(){
        this.name = "孙悟空";
        this.alias = "齐天大圣";
        System.out.println("name:"+this.name+",alias:"+this.alias);
    }

    public void monkeyShowTime(){
        System.out.println(this.name+": 我是"+this.alias+" "+this.name);
    }
}
public class MonkeyKingCopy implements Monkey {
    public MonkeyKingCopy(){
        System.out.println("六耳猕猴");
    }
}

首先,在不使用@Primary注解的情况下,配置两个真假猴王,看控制台输出信息。

@Bean(name = "monkey")
public Monkey monkey(){
    return new MonkeyKing();
}

@Bean(name = "monkey")
public Monkey monkeyCopy(){
    return new MonkeyKingCopy();
}
@SpringBootApplication
public class BeanAnnotationApplication {

    public static void main(String[] args) {
        SpringApplication.run(BeanAnnotationApplication.class, args);
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(AppConfig.class);

        Monkey monkey = context.getBean(Monkey.class);

        context.close();
    }

}

控制台结果:

No qualifying bean of type 'com.ramostear.bean.annotation.beans.Monkey' available: expected single matching bean but found 2: monkeyKing,monkey

现在,使用@Primary对monkey()方法进行注解,再次运行main()方法,此时,控制台将不再报错,假猴王已经被@Primary(如来)给控制了。

@Bean(name = "monkey")
@Primary
public Monkey monkey(){
    return new MonkeyKing();
}

控制台输出:

2020-03-25 17:00:31.811  INFO 14552 --- [           main] c.r.b.a.BeanAnnotationApplication        : Started BeanAnnotationApplication in 1.142 seconds (JVM running for 2.011)
name:孙悟空,alias:齐天大圣
美猴王出世...

7.与@Component一起使用

@Bean同样可以在被@Component注解的类中使用。在此情况下,@Bean注解的方法将被Spring容器视为普通工厂方法,并支持作用域和生命周期内的回调。值得注意的是,与@Component一起使用时,不能再像@Configuration注解那样,内部定义的bean方法不能再相互调用。

上述内容为使用@Bean注解的全部内容,需要的小伙伴可点击下面的链接移步到Github进行下载: