18 使用Java类的形式配置bean定义
除了传统的使用XML来配置底层的bean容器定义,Spring还支持使用大家熟悉的Java类的形式来进行配置。使用Java类的形式来进行配置时我们将使用一个Java类来作为配置的主体,并在类上使用@Configuration进行标注,表示其是一个配置类。然后将对应的bean定义都定义为Java配置类中的一个公用方法,并在方法上使用@Bean进行标注,表示其是一个bean定义。使用@Bean进行标注的方法对应的返回类型就是生成的bean定义对应的Class类型,对应的方法体实现就是我们用来产生对应bean定义的实例的过程,对应的方法名就是bean定义的默认beanName。下面我们先来简单的看一个对应的实现。
@Configuration
public class SpringConfig {
@Bean
public Hello hello() {
return new Hello();
}
}
在上面代码中我们就通过@Configuration标注了我们的类SpringConfig是一个Spring的配置类,然后在其中定义了一个使用@Bean进行标注的方法,Spring会将其作为一个bean定义添加到bean容器中,对应beanName为“hello”,然后直接new一个对应的实例作为bean定义的实例。之后我们就可以从该配置类产生的bean容器中获取对应的bean定义了,对应测试代码可以是如下这样。
@ContextConfiguration(classes={SpringConfig.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class ConfigurationTest {
@Autowired
private ApplicationContext context;
@Test
public void test() {
System.out.println(context.getBean("hello", Hello.class));
}
}
18.1 @Bean
在使用基于Java类进行配置时,我们可以使用@Bean来标注配置类中需要定义为bean容器中一个bean定义的方法。标注后,默认会以该方法的名称作为对应bean定义的beanName,以方法的返回类型作为对应bean定义的Class类型。
@Configuration
public class SpringConfig {
@Bean
public Hello hello() {
return new Hello();
}
}
如上示例,我们就在基于Java类的配置中使用@Bean注解定义了一个bean定义,其相当于我们在XML配置文件中的如下定义。
<bean id="hello" class="com.app.Hello"/>
18.1.1 指定beanName
默认情况Spring会以方法名称作为对应bean定义的beanName。如果用户希望指定对应bean定义的beanName,则可以通过@Bean的name属性进行指定。
@Bean(name="hello1")
public Hello hello() {
return new Hello();
}
我们也可以通过@Bean的name属性给bean定义指定多个名称,这个时候就可以将name属性以一个数组的形式进行指定。如下示例中我们就表示同时指定beanName为“hello”和“hello1”。
@Bean(name={"hello", "hello1"})
public Hello hello() {
return new Hello();
}
18.1.2 指定生命周期回调方法
根据之前文章的介绍我们知道bean的生命周期回调方法可以通过三种方式进行指定,使用JSR-250标准的@PostConstruct和@PreDestroy进行标注、实现InitializationBean和DisposableBean接口和通过bean定义的init-method和destroy-method进行指定。对于前两种方式而言,使用@Bean进行配置的bean也可以像其它基于注解或基于XML配置的bean一样进行回调。对于第三种方式,我们就需要通过@Bean的initMethod和destroyMethod属性进行指定,对应属性值即表示对应的回调方法名称。
@Bean(initMethod="init", destroyMethod="destroy")
public Hello hello() {
return new Hello();
}
上述示例中我们就通过initMethod属性指定了bean hello的初始化回调方法为init(),通过destroyMethod属性指定了对应的销毁前回调方法为destroy()。其就相当于我们基于XML配置的如下形式。
<bean id="hello" class="com.app.Hello" init-method="init" destroy-method="destroy"/>
18.1.3 指定是否自动注入
在使用XML进行配置的时候,我们可以通过其autowire指定是否需要进行自动注入。对应的我们可以在@Bean上使用autowire属性来指定是否进行自动注入。可选值有Autowire.NO、Autowire.BY_NAME和Autowire.BY_TYPE。默认是Autowire.NO,即不进行自动注入。Autowire.BY_NAME表示按名称进行自动注入,Autowire.BY_TYPE表示按类型进行自动注入。
@Bean(autowire=Autowire.BY_TYPE)
public Hello hello() {
return new Hello();
}
上述示例表示按照类型对依赖的bean进行自动注入。其等同于如下XML定义。
<bean id="hello" class="com.app.Hello" autowire="byType"/>
18.1.4 指定Scope
通过@Bean进行定义的bean对应的Scope默认也是singleton的,用户如果希望更改这种默认策略,则需要在@Bean对应的方法上使用@Scope标注进行定义,具体用法与我们之前介绍的在基于注解配置bean定义时是一样的。如下表示我们定义对应的Scope为prototype。
@Bean
@Scope("prototype")
public Hello hello() {
return new Hello();
}
如果需要被代理,可以指定proxyMode。
@Bean
@Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
public Hello hello() {
return new Hello();
}
18.1.5 依赖注入
对于处于同一个使用@Configuration进行标注的配置类中的bean注入,我们可以直接通过方法调用的形式来获取对应的bean实例以进行注入。如下示例中我们就通过调用world()方法获取了一个World实例,然后注入给了Hello的实例。
@Bean
public Hello hello() {
Hello hello = new Hello();
hello.setWorld(this.world());
return hello;
}
@Bean
public World world() {
return new World();
}
也可以像之前介绍的使用@Autowired或@Inject标注进行自动注入,即在对应的bean对应的Class类中进行定义。如下示例中我们就通过@Autowired定义了world需要进行自动注入。对于使用Java类进行配置的情况,Spring会自动扫描对应的注解以完成对应的bean定义。
public class Hello {
@Autowired
private World world;
}
再来看一个示例,在下面的示例中,我们调用了两次world()方法,分别获取对应的bean实例注入hello和beanA。因为Spring默认生成的bean实例是singleton类型的,为了实现这种机制,则在第一次调用world()产生了第一个实例后,Spring会将对应的对象缓存起来,待下次再调用world()方法获取实例时则直接获取缓存起来的那个实例。所以对于如下这种情况,实际上Spring也只会产生一个World实例。Spring的这种基于Java类配置的单例bean机制是通过CGLIB动态生成类来实现的,在系统启动时,Spring将通过CGLIB构建所有使用@Configuration进行标注的Class的子类,在子类调用父类对应方法产生对应的bean实例之前,会先从子类的缓存中获取,只有在取不到的情况下才会调用父类产生新的实例,产生了对应的实例后就将其缓存到对应的缓存中。另外,鉴于Spring的这种机制就要求我们对应的Java配置Class不是final型的,且存在一个无参构造方法。
@Configuration
public class SpringConfig {
@Bean
public Hello hello() {
Hello hello = new Hello();
hello.setWorld(world());
return hello;
}
@Bean
public World world() {
return new World();
}
@Bean
public BeanA beanA() {
BeanA beanA = new BeanA();
beanA.setWorld(world());
return beanA;
}
}
18.1.6 使用lookup-method
在之前介绍XML配置时,我们曾使用lookup-method的方式以解决单例bean引用多例bean的问题。在使用基于Java进行配置时如果我们也希望以这样的方式来解决单例bean引用多例bean的情况,则我们可以通过如下方式进行解决。打个比方我们有一个单例bean,对应类Hello,其需要引用一个多例的bean World,那么我们就可以在Hello中定义一个公用的方法以获取World实例,然后每次需要使用World实例时都通过该方法获取,如下的getWorld()方法就是这个功能。
public class Hello {
public void doSomething() {
World world = this.getWorld();
System.out.println(world);
//...
}
public World getWorld() {
return null;
}
}
然后我们在定义Hello对应的单例bean时,可以new一个匿名的Hello类的子类,然后在其中重写getWorld()方法,对应方法体则是通过@Configuration标注的配置类的对应方法获取一个多例的World实例。这样我们在单例的hello中每次调用getWorld()方法时都会从bean容器中获取到一个全新的World实例。
@Configuration
public class SpringConfig {
@Bean
public Hello hello() {
Hello hello = new Hello() {
public World getWorld() {
return world();
}
};
return hello;
}
@Bean
@Scope("prototype")
public World world() {
return new World();
}
}