欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring(17)——使用Java类的形式配置bean

程序员文章站 2022-05-29 14:36:12
...

17 使用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));
	}

}

17.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"/>

17.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();
}

17.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"/>

17.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"/>

17.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();
	}

17.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;
	}
	
}

17.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();
	}
	
}

 

 

本文转自:https://elim.iteye.com/blog/2389618