我们知道,如果想要将bean交由spring容器管理,就需要首先将bean注册在spring容器中,而bean可以通过xml或者注解的方式进行注册,基于xml的配置一般是通过
<bean>
、<context:component-scan>
等xml标签进行配置,然后由spring容器扫描xml文件进行注册;基于注解的注册主要是通过几种spring定义的注解进行配置,同样是由spring容器扫描并创建一些bean注册到容器中,spring基于注解的开发已经越来越流行,在spring boot等spring家族的框架中也是大量使用注解来驱动开发。学习spring注解的开发方式,对理解和学习spring boot有很大的帮助。
本文介绍基于注解注册bean的方式:
- 方式一: 使用
@Configuration
和@Bean
结合
@Configuration
public class CarConfig{
@Bean
public Car car() {
return new Car();
}
}
被@Configuration
注解标识的类自动获得@Component
的特性,因为该注解本身也是使用了@Component
注解,具体可以查看@Configuration
的源码定义,并且该类会作为spring的一个配置类,在创建该类型的bean
时,spring会扫描当中所有@Bean
注解标注的方法,并自动执行,返回值自动注册在容器中,默认使用方法名作为bean的name。也可以通过提供@Bean
的value值或设置bean的name属性来给bean起名字。
- 方式二:使用
@ComponentScan
注解自动注册
@ComponentScan("cn.wyn")
@Configuration
public class BookConfig {
}
package cn.wyn;
@Component
public class Book {
}
和在xml中配置<context:component-scan base-package="">
是类似的,通过在@Component
或者相关注解(比如@Controller
、@Configuration
、@Service
都是)标注的类上使用@ComponentScan
注解,spring会根据指定的扫描包路径进行扫描,自动创建所有标有@Component
相关注解的类的实例,并将其注册到spring容器中,如果是@Configuration标注的,还会执行其中的@Bean方法。
我们还可以对扫描的类进行过滤,比如扫描排除包含@Controller
的类:
@ComponentScan(value = "cn.wyn",
excludeFilters = { @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = {Controller.class})})
@Configuration
public class MyConfig {
}
上面的配置等同于xml配置:
<context:component-scan base-package="cn.wyn">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
比如只扫描包含@Service
注解的类:
@ComponentScan(value = "cn.wyn",
includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,
classes = Service.class)},
useDefaultFilters = false)
@Configuration
public class MyConfig {
}
上面的配置等同于xml配置:
<context:component-scan base-package="cn.wyn" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
配置include的同时还需要指定useDefaultFilters为false,这样spring就不会自动注册包下所有的Component。
备注: @ComponentScan
可以重复使用在同一个类上,用于实现多个扫描,但是这个特性需要使用jdk8
及以上版本的jdk,如果使用的jdk版本低于jdk8,可以使用@ComponentScans
来实现多个扫描。
我们还可以对扫描的规则进行自定义,通过指定include或者exclude的type值为CUSTOM
,指定处理规则的TypeFilter
类,我们需要自定义一个实现TypeFilter
接口的类,并重写match
方法:
class MyTypeFilte implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return false;
}
}
@Configuration
@ComponentScan(value = "cn.wyn",
includeFilters = { @ComponentScan.Filter(type = FilterType.CUSTOM,
classes = MyTypeFilter.class)},
useDefaultFilters = false)
class MyConfig {
}
说明:通过match方法的参数metadataReader可以获取正在扫描的类的元信息,比如类名,类上的注解信息等,match方法返回值如果是true,则是匹配。返回true的情况下,如果是include,则是注册,如果是exclude则是忽略。返回false则反之。
- 方式三: 使用@Import注解导入某个类注册到spring容器中
@Configuration
@Import({Car.class, Book.class})
class MyConfig {
}
通过在配置类上标注@Import注解,可以快速创建某个类的实例,并导入到spring容器中。
方式三扩展一: @Import 使用ImportSelector 批量导入:
具体方法是指定@Import的值为一个实现了ImportSelector
接口的类,该类重写selectImports
方法,selectImports
方法返回值为一个String数组,这个数组包含要导入的全限定类名。使用了ImportSelector
不会将ImportSelector实现类导入,只会将selectImports方法返回的数组指定的类导入。
class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"java.lang.String"};
}
}
@Configuration
@Import({MyImportSelector.class})
class BeanConfig {
}
方式三扩展二:@Import 使用ImportBeanDefinitionRegistrar
自定义一个ImportBeanDefinitionRegistrar类,实现ImportBeanDefinitionRegistrar
接口,重写registerBeanDefinitions
方法,通过参数registry
可以注册bean,比如:
class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//创建一个BeanDefinition对象
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Book.class);
//注册一个名为xixi的BeanDefinition
registry.registerBeanDefinition("xixi", rootBeanDefinition);
}
}
@Configuration
@Import({MyImportBeanDefinitionRegistrar.class})
class BeanConfig {
}
同样是使用@Import注解将ImportBeanDefinitionRegistrar导入,同样是只会将registerBeanDefinitions方法中注册的bean注册,不会将ImportDefinitionRegistrar这个类注册进来。
- 方式四:使用FactoryBean(工厂Bean)注册bean
实现FactoryBean
接口,实现以下三个方法:
方法名 | 作用 |
---|---|
getObject | 通过这个方法获得bean |
getObjectType | 通过这个方法获得Bean的Class对象 |
isSingleton | 通过这个方法来指定bean的作用域是否为单例 |
示例:
class MyFactoryBean implements FactoryBean {
//获取bean的具体方法
@Override
public Object getObject() throws Exception {
return new String("哈哈哈");
}
//获取bean的Class类型
@Override
public Class<?> getObjectType() {
return String.class;
}
//根据这个方法来指定bean是否单例
@Override
public boolean isSingleton() {
return true;
}
}
@Configuration
class BeanConfig {
@Bean("beanName")
public MyFactoryBean createBean() {
return new MyFactoryBean();
}
}
spring判断@Bean注解的方法的返回值是一个工厂Bean,会执行工厂bean的getObject方法获得一个实例,并注册到容器中,如果是单例,则只注册一次。而不是将FactoryBean的实现类注册进来。如果想要获得工厂bean本身这个实例,可以在获取bean的时候指定的bean name前加上“&”
前缀,如context.getBean("&bean")
设置bean的作用域--注解配置方式
bean的作用域有:
- singleton : 单例,整个应用中只存在一个实例bean
- prototype : 与单例相对,每次getBean都会重新生成一个Bean。
- request : web环境下,每个请求都会创建一个bean,在一次请求中只存在一个Bean,不同request的bean不同
- session : web环境下,session生命周期下,获取的是同一个bean
默认情况下是singleton单实例,可以通过以下方式来指定Bean的作用域。
@Configuration
class BeanConfig {
@Bean("book")
@Scope("prototype")
public Book createBean() {
return new Book();
}
}
bean 懒加载 --注解配置方式
默认情况下,所有单实例bean都会在创建spring容器的时候创建,如果在bean第一次使用的时候创建,我们称为懒加载
配置很简单,在创建bean的方法上添加@Lazy注解即可
@Configuration
class BeanConfig {
@Bean("book")
@Lazy
public Book createBean() {
return new Book();
}
}
按照条件注册Bean
我们可以通过某些条件,来选择是否注册Bean,通过@Condition注解来实现。
@Configuration
class BeanConfig {
@Bean("book")
@Conditional(MyCondition.class)
public Book createBean() {
return new Book();
}
}
//实现Condition接口,并重写matches方法,根据该方法返回的布尔值来决定是否注册Bean
class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
/**
* 根据环境变量是否存在my.env=hello的属性来决定是否创建,
* 可以通过启动参数指定-Dmy.env=hello来测试。
**/
Environment environment = context.getEnvironment();
String property = environment.getProperty("my.env");
if ("hello".equals(property)) {
return true;
}
return false;
}
}
//测试
public class MainTest {
@Test
public void test() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
}
}
//测试用到的类
class Book {
public Book() {
System.out.println("init book ...");
}
}
@Conditional注解可以添加在方法上也可以添加在类上,放在类上是对类中所有@Bean方法统一设置。
Profile 的使用
日常开发中,我们可能需要根据不同的环境来注册一套不同的Bean,比如:我们生产环境、测试环境、开发环境会使用不同的数据源。通过Profile配置,就可以指定该Bean是在某个Profile被激活时才会注册到spring容器中,这与maven中的profile是一个道理。
如下:
@Profile("dev")
public Book book1(){
return new Book();
}
@Profile("test") Book book2() {
return new Book();
}
@Profile("prod") Book book3() {
return new Book();
}
通过指定环境变量,或者jvm启动参数:-Dspring.profiles.active=dev
都可以来激活profile,也可以在代码中激活profile,如:
public class MainTest {
@Test
public void test() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//设置激活的profile
context.getEnvironment().setActiveProfiles("dev");
context.register(BeanConfig.class);
context.refresh();
}
}
@Configuration
class BeanConfig {
@Bean
public Book book(){
return new Book();
}
@Bean
@Profile("dev")
public Book book1(){
return new Book();
}
@Bean
@Profile("test") Book book2() {
return new Book();
}
@Bean
@Profile("prod") Book book3() {
return new Book();
}
}
@Profile注解同样可以写在配置类上,整个配置类的所有配置会在指定profile下才激活。
转载请注明出处
作者:Coder_Ring
原文链接:https://www.jianshu.com/p/bdca18850673